From 9d79e86da4a8efa2c8bc3af7621adf7aa2c6525a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?colin=20axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Wed, 5 Jan 2022 13:32:12 +0100 Subject: [PATCH 001/140] Backport commits from main to v3 release branch (#682) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * reorganize channel handshake handler (#647) * reorganize channel handshake handler split out channel state changes into its own function. * readjust 27-interchain-accounts to not rely on state being set before the application callback * add changelog and migration doc entry * Update modules/core/04-channel/keeper/handshake.go * docs: ICA Overview (#626) * docs: ica overview * fix: ordering * add spacing * fix: spacing * fix: remove bulletpoints * fix: wording * update go mod for security vulnerabilities (#655) * update go mod for security vulnerabilities * update package-lock.json * update vue dependency (#662) * bump glob-parent version in json package (#663) * build(deps): bump actions/setup-go from 2.1.4 to 2.1.5 (#656) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 2.1.4 to 2.1.5. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v2.1.4...v2.1.5) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> * docs: begin removal of internal "spec" directories (#634) * begin removal of spec docs within core ibc * remove broken link * Apply suggestions from code review Co-authored-by: Damian Nolan * remove broken link * remove broken links * Apply suggestions from code review Co-authored-by: Carlos Rodriguez Co-authored-by: Damian Nolan Co-authored-by: Carlos Rodriguez * Modify `OnChanOpenTry` application callback to perform app version negotitation (#646) * remove NegotiateAppVersion and AppVersion gRPC (#643) The NegotiateAppVersion callback has been removed from the IBC Application interface. The gRPC AppVersion has been removed. The app version negoitation will be handled by applications by returning the version in OnChanOpenTry. * Modify `OnChanOpenTry` to return application version (#650) * modify OnChanOpenTry to return negotiated version modify IBCModule interface function OnChanOpenTry to return the negotiated app version. Tests have not been updated * fix ibc_module_test.go tests * fix tests * Apply suggestions from code review * add handshake test case * add CHANGELOG and migration docs * update documentation * fix broken link * fix broken link (#664) * chore: update make build-docs, add docs build checker (#667) * update Makefile, add docs build checker * Update .github/workflows/check-docs.yml Co-authored-by: Marko Co-authored-by: Marko * register ICA query server, fix panics in params query cli (#666) Register the controller and host query servers to a chain. Returns an error upon cli params query failure instead of panicing. * update of roadmap with latest release (#653) * update of roadmap with latest release and changed the way release versions are encoded * fixed typo Co-authored-by: Carlos Rodriguez * build(deps): bump actions/checkout from 2.3.1 to 2.4.0 (#672) Bumps [actions/checkout](https://github.com/actions/checkout) from 2.3.1 to 2.4.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2.3.1...v2.4.0) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * allow ics20 to connect to middleware (#675) * allow ics20 to connect to middleware Creates ics4Wrapper which allows middleware applications to only implement SendPacket to connect ics20 as an application in its middleware stack * add changelog and migration doc * fix migration doc spelling * fix: register InterchainAccount as x/auth GenesisAccount (#676) * adding GenesisAccount interface registration for InterchainAccount impl * updating RegisterInterfaces ica godoc * enable mergify for backports (#678) Co-authored-by: Carlos Rodriguez Co-authored-by: Sean King Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Damian Nolan Co-authored-by: Carlos Rodriguez Co-authored-by: Marko Co-authored-by: Carlos Rodriguez --- .github/mergify.yml | 52 + .github/workflows/check-docs.yml | 26 + .github/workflows/link-check.yml | 2 +- .github/workflows/test.yml | 14 +- .mergify.yml | 10 - CHANGELOG.md | 3 + Makefile | 4 +- .../interchain-accounts/overview.md | 27 + docs/ibc/apps.md | 50 +- .../spec/06_events.md => docs/ibc/events.md | 4 +- docs/ibc/middleware/develop.md | 65 +- docs/ibc/overview.md | 90 +- .../spec/07_params.md => docs/ibc/params.md | 6 +- docs/ibc/proto-docs.md | 72 +- docs/ibc/relayer.md | 4 +- docs/migrations/v2-to-v3.md | 37 +- docs/package-lock.json | 4490 ++++++++++------- docs/package.json | 2 + docs/roadmap/roadmap.md | 73 +- go.mod | 3 + go.sum | 12 +- .../controller/client/cli/query.go | 6 +- .../controller/ibc_module.go | 17 +- .../controller/ibc_module_test.go | 61 +- .../controller/keeper/grpc_query.go | 2 + .../controller/keeper/handshake.go | 7 +- .../controller/keeper/handshake_test.go | 7 - .../host/client/cli/query.go | 6 +- .../27-interchain-accounts/host/ibc_module.go | 19 +- .../host/ibc_module_test.go | 76 +- .../host/keeper/handshake.go | 42 +- .../host/keeper/handshake_test.go | 33 +- .../host/keeper/keeper.go | 21 - .../host/keeper/keeper_test.go | 2 +- .../host/keeper/relay_test.go | 2 + modules/apps/27-interchain-accounts/module.go | 2 + .../27-interchain-accounts/types/codec.go | 5 +- .../types/expected_keepers.go | 2 +- modules/apps/transfer/client/cli/query.go | 6 +- modules/apps/transfer/ibc_module.go | 41 +- modules/apps/transfer/ibc_module_test.go | 11 +- modules/apps/transfer/keeper/keeper.go | 4 +- modules/apps/transfer/keeper/keeper_test.go | 2 + modules/apps/transfer/keeper/relay.go | 2 +- modules/apps/transfer/transfer_test.go | 2 + .../apps/transfer/types/expected_keepers.go | 6 +- modules/core/04-channel/keeper/handshake.go | 127 +- .../core/04-channel/keeper/handshake_test.go | 15 +- modules/core/04-channel/keeper/keeper.go | 10 + modules/core/04-channel/types/msgs.go | 2 + modules/core/04-channel/types/tx.pb.go | 6 +- modules/core/05-port/keeper/grpc_query.go | 52 - .../core/05-port/keeper/grpc_query_test.go | 103 - modules/core/05-port/types/module.go | 32 +- modules/core/24-host/doc.go | 9 + modules/core/keeper/grpc_query.go | 6 - modules/core/keeper/msg_server.go | 90 +- modules/core/spec/01_concepts.md | 188 - modules/core/spec/02_state.md | 28 - modules/core/spec/03_state_transitions.md | 106 - modules/core/spec/04_messages.md | 497 -- modules/core/spec/05_callbacks.md | 80 - modules/core/spec/README.md | 26 - modules/core/types/query.go | 4 - .../06-solomachine/spec/02_state.md | 3 - .../06-solomachine/spec/04_messages.md | 8 - .../06-solomachine/spec/README.md | 1 - proto/ibc/core/channel/v1/tx.proto | 6 +- proto/ibc/core/port/v1/query.proto | 35 - testing/endpoint.go | 4 + testing/mock/ibc_app.go | 16 +- testing/mock/ibc_module.go | 31 +- testing/simapp/app.go | 2 +- testing/values.go | 2 +- 74 files changed, 3278 insertions(+), 3641 deletions(-) create mode 100644 .github/mergify.yml create mode 100644 .github/workflows/check-docs.yml delete mode 100644 .mergify.yml rename modules/core/spec/06_events.md => docs/ibc/events.md (99%) rename modules/core/spec/07_params.md => docs/ibc/params.md (88%) delete mode 100644 modules/core/05-port/keeper/grpc_query.go delete mode 100644 modules/core/05-port/keeper/grpc_query_test.go create mode 100644 modules/core/24-host/doc.go delete mode 100644 modules/core/spec/02_state.md delete mode 100644 modules/core/spec/03_state_transitions.md delete mode 100644 modules/core/spec/04_messages.md delete mode 100644 modules/core/spec/05_callbacks.md delete mode 100644 modules/core/spec/README.md delete mode 100644 modules/light-clients/06-solomachine/spec/04_messages.md delete mode 100644 proto/ibc/core/port/v1/query.proto diff --git a/.github/mergify.yml b/.github/mergify.yml new file mode 100644 index 00000000000..83b6f2d771c --- /dev/null +++ b/.github/mergify.yml @@ -0,0 +1,52 @@ +queue_rules: + - name: default + conditions: + - "#approved-reviews-by>=1" + - base=main + - label=automerge + +pull_request_rules: + - name: automerge to main with label automerge and branch protection passing + conditions: + - "#approved-reviews-by>=1" + - base=main + - label=automerge + actions: + queue: + name: default + method: squash + commit_message_template: | + {{ title }} (#{{ number }}) + {{ body }} + - name: backport patches to v1.1.x branch + conditions: + - base=main + - label=backport-to-v1.1.x + actions: + backport: + branches: + - release/v1.1.x + - name: backport patches to v1.2x branch + conditions: + - base=main + - label=backport-to-v1.2.x + actions: + backport: + branches: + - release/v1.2.x + - name: backport patches to v2.0.x branch + conditions: + - base=main + - label=backport-to-v2.0.x + actions: + backport: + branches: + - release/v2.0.x + - name: backport patches to v3.0.x branch + conditions: + - base=main + - label=backport-to-v3.0.x + actions: + backport: + branches: + - release/v3.0.x \ No newline at end of file diff --git a/.github/workflows/check-docs.yml b/.github/workflows/check-docs.yml new file mode 100644 index 00000000000..abcaa066e2d --- /dev/null +++ b/.github/workflows/check-docs.yml @@ -0,0 +1,26 @@ +name: Check docs build +# This workflow runs when a PR is labeled with `docs` +# This will check if the docs build successfully by running `npm run build` +on: + pull_request: + paths: + - './docs' + +jobs: + check-docs-build: + if: ${{ github.event.label.name == 'docs' }} + + name: Check docs build + runs-on: ubuntu-latest + steps: + - name: Checkout 🛎️ + uses: actions/checkout@v2.4.0 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Install dependencies and build docs 🧱 + run: | + cd docs + npm install + npm run build diff --git a/.github/workflows/link-check.yml b/.github/workflows/link-check.yml index c802efe8616..d40882e4fde 100644 --- a/.github/workflows/link-check.yml +++ b/.github/workflows/link-check.yml @@ -4,7 +4,7 @@ jobs: markdown-link-check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v2.4.0 - uses: gaurav-nelson/github-action-markdown-link-check@v1 with: config-file: '.github/workflows/link-check-config.json' \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 94c37cdf187..730859ebd3b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ jobs: install-tparse: runs-on: ubuntu-latest steps: - - uses: actions/setup-go@v2.1.4 + - uses: actions/setup-go@v2.1.5 with: go-version: 1.17 - name: Display go version @@ -37,8 +37,8 @@ jobs: matrix: go-arch: ["amd64", "arm", "arm64"] steps: - - uses: actions/checkout@v2.3.4 - - uses: actions/setup-go@v2.1.4 + - uses: actions/checkout@v2.4.0 + - uses: actions/setup-go@v2.1.5 with: go-version: 1.17 - uses: technote-space/get-diff-action@v5 @@ -54,7 +54,7 @@ jobs: split-test-files: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.3.4 + - uses: actions/checkout@v2.4.0 - name: Create a file with all the pkgs run: go list ./... > pkgs.txt - name: Split pkgs into 4 files @@ -85,8 +85,8 @@ jobs: matrix: part: ["00", "01", "02", "03"] steps: - - uses: actions/checkout@v2.3.4 - - uses: actions/setup-go@v2.1.4 + - uses: actions/checkout@v2.4.0 + - uses: actions/setup-go@v2.1.5 with: go-version: 1.17 - uses: technote-space/get-diff-action@v5 @@ -112,7 +112,7 @@ jobs: runs-on: ubuntu-latest needs: tests steps: - - uses: actions/checkout@v2.3.4 + - uses: actions/checkout@v2.4.0 - uses: technote-space/get-diff-action@v5 with: PATTERNS: | diff --git a/.mergify.yml b/.mergify.yml deleted file mode 100644 index 72cd7245ff8..00000000000 --- a/.mergify.yml +++ /dev/null @@ -1,10 +0,0 @@ -pull_request_rules: - - name: automerge to main with label automerge and branch protection passing - conditions: - - "#approved-reviews-by>=1" - - base=main - - label=automerge - actions: - merge: - method: squash - strict: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e22659c6fc..9f1ecf9039f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,8 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### API Breaking +* (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. @@ -54,6 +56,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * [\#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/issues/288) Making the 05-port keeper function IsBound public. The IsBound function checks if the provided portID is already binded to a module. * (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. ### Features diff --git a/Makefile b/Makefile index 82c75b862ef..f424693048e 100644 --- a/Makefile +++ b/Makefile @@ -196,11 +196,13 @@ godocs: build-docs: @cd docs && \ while read -r branch path_prefix; do \ - (git checkout $${branch} && npm install && VUEPRESS_BASE="/$${path_prefix}/" npm run build) ; \ + echo "building branch $${branch}" ; \ + (git clean -fdx && git reset --hard && git checkout $${branch} && npm install && VUEPRESS_BASE="/$${path_prefix}/" npm run build) ; \ mkdir -p ~/output/$${path_prefix} ; \ cp -r .vuepress/dist/* ~/output/$${path_prefix}/ ; \ cp ~/output/$${path_prefix}/index.html ~/output ; \ done < versions ; + .PHONY: build-docs ############################################################################### diff --git a/docs/app_modules/interchain-accounts/overview.md b/docs/app_modules/interchain-accounts/overview.md index 9f8983c55bb..bc8522c726e 100644 --- a/docs/app_modules/interchain-accounts/overview.md +++ b/docs/app_modules/interchain-accounts/overview.md @@ -2,4 +2,31 @@ order: 1 --> +# Overview + +Learn about what the Interchain Accounts module is, and how to build custom modules that utilize Interchain Accounts functionality {synopsis} + + +# 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. + diff --git a/docs/ibc/apps.md b/docs/ibc/apps.md index 6a6b39ba8d7..bb2716fa0b1 100644 --- a/docs/ibc/apps.md +++ b/docs/ibc/apps.md @@ -71,9 +71,8 @@ OnChanOpenTry( channelID string, channelCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, - version, counterpartyVersion string, -) error { +) (string, error) { // Module may have already claimed capability in OnChanOpenInit in the case of crossing hellos // (ie chainA and chainB both call ChanOpenInit before one of them calls ChanOpenTry) // If the module can already authenticate the capability then the module already owns it so we don't need to claim @@ -88,8 +87,18 @@ OnChanOpenTry( // ... do custom initialization logic // Use above arguments to determine if we want to abort handshake - err := checkArguments(args) - return err + 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 @@ -157,38 +166,11 @@ OnChanCloseConfirm( 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 verify that the `MsgChanOpenTry.Version` is valid and that `MsgChanOpenTry.CounterpartyVersion` 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 implement the `NegotiateAppVersion` method from the `IBCModule` -interface. This method performs application version negotiation and returns the negotiated version. -If the version cannot be negotiated, an error should be returned. - -```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) { - // do custom application version negotiation logic -} -``` - -This function `NegotiateAppVersion` returns the version to be used in the `ChanOpenTry` step -(`MsgChanOpenTry.Version`). The relayer chooses the initial version in the `ChanOpenInit` step -(this will likely be chosen by the user controlling the relayer or by the application that -triggers the `ChanOpenInit` step). - -The version submitted in the `ChanOpenInit` step (`MsgChanOpenInit.Version`) is passed as an -argument (`proposedVersion`) to the function `NegotiateAppVersion`. This function looks at -the `proposedVersion` and returns the matching version to be used in the `ChanOpenTry` step. -Applications can choose to implement this in however fashion they choose. +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 diff --git a/modules/core/spec/06_events.md b/docs/ibc/events.md similarity index 99% rename from modules/core/spec/06_events.md rename to docs/ibc/events.md index 8a416217e1f..b7b28351cab 100644 --- a/modules/core/spec/06_events.md +++ b/docs/ibc/events.md @@ -1,9 +1,11 @@ # Events +**NOTE**: 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 attirbute 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 diff --git a/docs/ibc/middleware/develop.md b/docs/ibc/middleware/develop.md index b4cd037e5e6..1d75e3965a8 100644 --- a/docs/ibc/middleware/develop.md +++ b/docs/ibc/middleware/develop.md @@ -103,29 +103,32 @@ func OnChanOpenTry( channelID string, channelCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, - version, counterpartyVersion string, -) error { - // core/04-channel/types contains a helper function to split middleware and underlying app version - cpMiddlewareVersion, cpAppVersion = channeltypes.SplitChannelVersion(counterpartyVersion) - middlewareVersion, appVersion = channeltypes.SplitChannelVersion(version) - if !isCompatible(cpMiddlewareVersion, middlewareVersion) { - return error - } - doCustomLogic() - - // call the underlying applications OnChanOpenTry callback - app.OnChanOpenTry( - ctx, - order, - connectionHops, - portID, - channelID, - channelCap, - counterparty, - cpAppVersion, // note we only pass counterparty app version here - appVersion, // only pass app version - ) +) (string, error) { + doCustomLogic() + + // core/04-channel/types contains a helper function to split middleware and underlying app version + cpMiddlewareVersion, cpAppVersion = channeltypes.SplitChannelVersion(counterpartyVersion) + + // call the underlying applications OnChanOpenTry callback + appVersion, err := app.OnChanOpenTry( + ctx, + order, + connectionHops, + portID, + channelID, + channelCap, + counterparty, + cpAppVersion, // note we only pass counterparty app version here + ) + if err != nil { + return err + } + + middlewareVersion := negotiateMiddlewareVersion(cpMiddlewareVersion) + version := constructVersion(middlewareVersion, appVersion) + + return version } func OnChanOpenAck( @@ -134,15 +137,15 @@ func OnChanOpenAck( channelID string, counterpartyVersion string, ) error { - // core/04-channel/types contains a helper function to split middleware and underlying app version - middlewareVersion, appVersion = channeltypes.SplitChannelVersion(version) - if !isCompatible(middlewareVersion) { - return error - } - doCustomLogic() + // core/04-channel/types contains a helper function to split middleware and underlying app version + middlewareVersion, appVersion = channeltypes.SplitChannelVersion(version) + if !isCompatible(middlewareVersion) { + return error + } + doCustomLogic() - // call the underlying applications OnChanOpenTry callback - app.OnChanOpenAck(ctx, portID, channelID, appVersion) + // call the underlying applications OnChanOpenTry callback + app.OnChanOpenAck(ctx, portID, channelID, appVersion) } func OnChanOpenConfirm( @@ -236,4 +239,4 @@ func SendPacket(appPacket channeltypes.Packet) { return ics4Keeper.SendPacket(packet) } -``` \ No newline at end of file +``` diff --git a/docs/ibc/overview.md b/docs/ibc/overview.md index 563a56167ad..53ad64e08e8 100644 --- a/docs/ibc/overview.md +++ b/docs/ibc/overview.md @@ -37,10 +37,19 @@ Read on for a detailed explanation of how to write a self-contained IBC applicat ### [Clients](https://github.com/cosmos/ibc-go/blob/main/modules/core/02-client) -IBC clients are light clients that are 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. +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 `CosnensusState`. 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: @@ -49,6 +58,60 @@ The supported IBC clients are: * [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 @@ -67,6 +130,8 @@ of a handshake or a packet intended to be relayed to a module on the counterpart 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/ics/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. @@ -138,11 +203,27 @@ If all handshake steps are successful, the channel is opened on both sides. At e 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 @@ -211,7 +292,6 @@ The original sender module then executes application-specific acknowledgment log If you want to learn more about IBC, check the following specifications: * [IBC specification overview](https://github.com/cosmos/ibc/blob/master/README.md) -* [IBC SDK specification](../../modules/core/spec/README.md) ## Next {hide} diff --git a/modules/core/spec/07_params.md b/docs/ibc/params.md similarity index 88% rename from modules/core/spec/07_params.md rename to docs/ibc/params.md index 67e79ef81dd..3040eea50b7 100644 --- a/modules/core/spec/07_params.md +++ b/docs/ibc/params.md @@ -1,12 +1,12 @@ # Parameters -## Clients +## 02-Client -The ibc clients contain the following parameters: +The 02-client submodule contains the following parameters: | Key | Type | Default Value | |------------------|------|---------------| diff --git a/docs/ibc/proto-docs.md b/docs/ibc/proto-docs.md index c3b10abc545..8baaa8eda1a 100644 --- a/docs/ibc/proto-docs.md +++ b/docs/ibc/proto-docs.md @@ -207,12 +207,6 @@ - [Msg](#ibc.core.connection.v1.Msg) -- [ibc/core/port/v1/query.proto](#ibc/core/port/v1/query.proto) - - [QueryAppVersionRequest](#ibc.core.port.v1.QueryAppVersionRequest) - - [QueryAppVersionResponse](#ibc.core.port.v1.QueryAppVersionResponse) - - - [Query](#ibc.core.port.v1.Query) - - [ibc/core/types/v1/genesis.proto](#ibc/core/types/v1/genesis.proto) - [GenesisState](#ibc.core.types.v1.GenesisState) @@ -1825,14 +1819,15 @@ MsgChannelOpenInitResponse defines the Msg/ChannelOpenInit response type. ### MsgChannelOpenTry MsgChannelOpenInit defines a msg sent by a Relayer to try to open a channel -on Chain B. +on Chain B. The version field within the Channel field has been deprecated. Its +value will be ignored by core IBC. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | `port_id` | [string](#string) | | | | `previous_channel_id` | [string](#string) | | in the case of crossing hello's, when both chains call OpenInit, we need the channel identifier of the previous channel in state INIT | -| `channel` | [Channel](#ibc.core.channel.v1.Channel) | | | +| `channel` | [Channel](#ibc.core.channel.v1.Channel) | | NOTE: the version field within the channel has been deprecated. Its value will be ignored by core IBC. | | `counterparty_version` | [string](#string) | | | | `proof_init` | [bytes](#bytes) | | | | `proof_height` | [ibc.core.client.v1.Height](#ibc.core.client.v1.Height) | | | @@ -3107,67 +3102,6 @@ Msg defines the ibc/connection Msg service. - -

Top

- -## ibc/core/port/v1/query.proto - - - - - -### QueryAppVersionRequest -QueryAppVersionRequest is the request type for the Query/AppVersion RPC method - - -| Field | Type | Label | Description | -| ----- | ---- | ----- | ----------- | -| `port_id` | [string](#string) | | port unique identifier | -| `connection_id` | [string](#string) | | connection unique identifier | -| `ordering` | [ibc.core.channel.v1.Order](#ibc.core.channel.v1.Order) | | whether the channel is ordered or unordered | -| `counterparty` | [ibc.core.channel.v1.Counterparty](#ibc.core.channel.v1.Counterparty) | | counterparty channel end | -| `proposed_version` | [string](#string) | | proposed version | - - - - - - - - -### QueryAppVersionResponse -QueryAppVersionResponse is the response type for the Query/AppVersion RPC method. - - -| Field | Type | Label | Description | -| ----- | ---- | ----- | ----------- | -| `port_id` | [string](#string) | | port id associated with the request identifiers | -| `version` | [string](#string) | | supported app version | - - - - - - - - - - - - - - -### Query -Query defines the gRPC querier service - -| Method Name | Request Type | Response Type | Description | HTTP Verb | Endpoint | -| ----------- | ------------ | ------------- | ------------| ------- | -------- | -| `AppVersion` | [QueryAppVersionRequest](#ibc.core.port.v1.QueryAppVersionRequest) | [QueryAppVersionResponse](#ibc.core.port.v1.QueryAppVersionResponse) | AppVersion queries an IBC Port and determines the appropriate application version to be used | | - - - - -

Top

diff --git a/docs/ibc/relayer.md b/docs/ibc/relayer.md index 29699d7cb3c..c846f2c7320 100644 --- a/docs/ibc/relayer.md +++ b/docs/ibc/relayer.md @@ -1,5 +1,5 @@ # Relayer @@ -14,7 +14,7 @@ order: 4 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 spec](https://github.com/cosmos/ibc-go/blob/main/modules/core/spec/06_events.md). +the [IBC events document](./events.md). 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 diff --git a/docs/migrations/v2-to-v3.md b/docs/migrations/v2-to-v3.md index 9f0f130300d..21abf4156ef 100644 --- a/docs/migrations/v2-to-v3.md +++ b/docs/migrations/v2-to-v3.md @@ -18,10 +18,42 @@ No genesis or in-place migrations are required when upgrading from v1 or v2 of i ## 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](../app_modules/interchain-accounts/overview.md) for more information. ## 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 negoitation 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. + +### `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`. @@ -34,7 +66,10 @@ Please review the [mock](../../testing/mock/ibc_module.go) and [transfer](../../ ## Relayers -- No relevant changes were made in this release. +`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 diff --git a/docs/package-lock.json b/docs/package-lock.json index b921c516323..d63a43a3fac 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -5,9 +5,12 @@ "requires": true, "packages": { "": { + "name": "docs", "version": "1.0.0", "license": "ISC", "dependencies": { + "glob-parent": "^5.1.2", + "vue": "^2.6.14", "vuepress-theme-cosmos": "^1.0.182" }, "devDependencies": { @@ -130,32 +133,38 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", + "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", "dependencies": { - "@babel/highlight": "^7.12.13" + "@babel/highlight": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.13.15", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.13.15.tgz", - "integrity": "sha512-ltnibHKR1VnrU4ymHyQ/CXtNXI6yZC0oJThyW78Hft8XndANwi+9H+UIklBDraIjFEJzw8wmcM427oDd9KS5wA==" + "version": "7.16.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.4.tgz", + "integrity": "sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q==", + "engines": { + "node": ">=6.9.0" + } }, "node_modules/@babel/core": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.13.16.tgz", - "integrity": "sha512-sXHpixBiWWFti0AV2Zq7avpTasr6sIAu7Y396c608541qAU2ui4a193m0KSQmfPSKFZLnQ3cvlKDOm3XkuXm3Q==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.13.16", - "@babel/helper-compilation-targets": "^7.13.16", - "@babel/helper-module-transforms": "^7.13.14", - "@babel/helpers": "^7.13.16", - "@babel/parser": "^7.13.16", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.13.15", - "@babel/types": "^7.13.16", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.5.tgz", + "integrity": "sha512-wUcenlLzuWMZ9Zt8S0KmFwGlH6QKRh3vsm/dhDA3CHkiTA45YuG1XkHRcNRl73EFPXDp/d5kVOU0/y7x2w6OaQ==", + "dependencies": { + "@babel/code-frame": "^7.16.0", + "@babel/generator": "^7.16.5", + "@babel/helper-compilation-targets": "^7.16.3", + "@babel/helper-module-transforms": "^7.16.5", + "@babel/helpers": "^7.16.5", + "@babel/parser": "^7.16.5", + "@babel/template": "^7.16.0", + "@babel/traverse": "^7.16.5", + "@babel/types": "^7.16.0", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -172,9 +181,9 @@ } }, "node_modules/@babel/core/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dependencies": { "ms": "2.1.2" }, @@ -215,13 +224,16 @@ } }, "node_modules/@babel/generator": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.16.tgz", - "integrity": "sha512-grBBR75UnKOcUWMp8WoDxNsWCFl//XCK6HWTrBQKTr5SV9f5g0pNOjdyzi/DTBv12S9GnYPInIXQBTky7OXEMg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.5.tgz", + "integrity": "sha512-kIvCdjZqcdKqoDbVVdt5R99icaRtrtYhYK/xux5qiWCBmfdvEYMFZ68QCrpE5cbFM1JsuArUNs1ZkuKtTtUcZA==", "dependencies": { - "@babel/types": "^7.13.16", + "@babel/types": "^7.16.0", "jsesc": "^2.5.1", "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/generator/node_modules/source-map": { @@ -233,67 +245,84 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz", - "integrity": "sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.0.tgz", + "integrity": "sha512-ItmYF9vR4zA8cByDocY05o0LGUkp1zhbTQOH1NFyl5xXEqlTJQCEJjieriw+aFpxo16swMxUnUiKS7a/r4vtHg==", "dependencies": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.12.13.tgz", - "integrity": "sha512-CZOv9tGphhDRlVjVkAgm8Nhklm9RzSmWpX2my+t7Ua/KT616pEzXsQCjinzvkRvHWJ9itO4f296efroX23XCMA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.5.tgz", + "integrity": "sha512-3JEA9G5dmmnIWdzaT9d0NmFRgYnWUThLsDaL7982H0XqqWr56lRrsmwheXFMjR+TMl7QMBb6mzy9kvgr1lRLUA==", "dependencies": { - "@babel/helper-explode-assignable-expression": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/helper-explode-assignable-expression": "^7.16.0", + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.16.tgz", - "integrity": "sha512-3gmkYIrpqsLlieFwjkGgLaSHmhnvlAYzZLlYVjlW+QwI+1zE17kGxuJGmIqDQdYp56XdmGeD+Bswx0UTyG18xA==", + "version": "7.16.3", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.3.tgz", + "integrity": "sha512-vKsoSQAyBmxS35JUOOt+07cLc6Nk/2ljLIHwmq2/NM6hdioUaqEXq/S+nXvbvXbZkNDlWOymPanJGOc4CBjSJA==", "dependencies": { - "@babel/compat-data": "^7.13.15", - "@babel/helper-validator-option": "^7.12.17", - "browserslist": "^4.14.5", + "@babel/compat-data": "^7.16.0", + "@babel/helper-validator-option": "^7.14.5", + "browserslist": "^4.17.5", "semver": "^6.3.0" }, + "engines": { + "node": ">=6.9.0" + }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.13.11", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.13.11.tgz", - "integrity": "sha512-ays0I7XYq9xbjCSvT+EvysLgfc3tOkwCULHjrnscGT3A9qD4sk3wXnJ3of0MAWsWGjdinFvajHU2smYuqXKMrw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.5.tgz", + "integrity": "sha512-NEohnYA7mkB8L5JhU7BLwcBdU3j83IziR9aseMueWGeAjblbul3zzb8UvJ3a1zuBiqCMObzCJHFqKIQE6hTVmg==", "dependencies": { - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-member-expression-to-functions": "^7.13.0", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/helper-replace-supers": "^7.13.0", - "@babel/helper-split-export-declaration": "^7.12.13" + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-environment-visitor": "^7.16.5", + "@babel/helper-function-name": "^7.16.0", + "@babel/helper-member-expression-to-functions": "^7.16.5", + "@babel/helper-optimise-call-expression": "^7.16.0", + "@babel/helper-replace-supers": "^7.16.5", + "@babel/helper-split-export-declaration": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.12.17", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.17.tgz", - "integrity": "sha512-p2VGmBu9oefLZ2nQpgnEnG0ZlRPvL8gAGvPUMQwUdaE8k49rOMuZpOwdQoy5qJf6K8jL3bcAMhVUlHAjIgJHUg==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.16.0.tgz", + "integrity": "sha512-3DyG0zAFAZKcOp7aVr33ddwkxJ0Z0Jr5V99y3I690eYLpukJsJvAbzTy1ewoCqsML8SbIrjH14Jc/nSQ4TvNPA==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.12.13", + "@babel/helper-annotate-as-pure": "^7.16.0", "regexpu-core": "^4.7.1" }, + "engines": { + "node": ">=6.9.0" + }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.0.tgz", - "integrity": "sha512-JT8tHuFjKBo8NnaUbblz7mIu1nnvUDiHVjXXkulZULyidvo/7P6TY7+YqpV37IfF+KUFxmlK04elKtGKXaiVgw==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.0.tgz", + "integrity": "sha512-7hfT8lUljl/tM3h+izTX/pO3W3frz2ok6Pk+gzys8iJqDfZrZy2pXjRTZAvG2YmfHun1X4q8/UZRLatMfqc5Tg==", "dependencies": { "@babel/helper-compilation-targets": "^7.13.0", "@babel/helper-module-imports": "^7.12.13", @@ -309,9 +338,9 @@ } }, "node_modules/@babel/helper-define-polyfill-provider/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dependencies": { "ms": "2.1.2" }, @@ -329,175 +358,243 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.5.tgz", + "integrity": "sha512-ODQyc5AnxmZWm/R2W7fzhamOk1ey8gSguo5SGvF0zcB3uUzRpTRmM/jmLSm9bDMyPlvbyJ+PwPEK0BWIoZ9wjg==", + "dependencies": { + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-explode-assignable-expression": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.13.0.tgz", - "integrity": "sha512-qS0peLTDP8kOisG1blKbaoBg/o9OSa1qoumMjTK5pM+KDTtpxpsiubnCGP34vK8BXGcb2M9eigwgvoJryrzwWA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.0.tgz", + "integrity": "sha512-Hk2SLxC9ZbcOhLpg/yMznzJ11W++lg5GMbxt1ev6TXUiJB0N42KPC+7w8a+eWGuqDnUYuwStJoZHM7RgmIOaGQ==", "dependencies": { - "@babel/types": "^7.13.0" + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", - "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz", + "integrity": "sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog==", "dependencies": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/helper-get-function-arity": "^7.16.0", + "@babel/template": "^7.16.0", + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz", + "integrity": "sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ==", "dependencies": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.13.16.tgz", - "integrity": "sha512-1eMtTrXtrwscjcAeO4BVK+vvkxaLJSPFz1w1KLawz6HLNi9bPFGBNwwDyVfiu1Tv/vRRFYfoGaKhmAQPGPn5Wg==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz", + "integrity": "sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg==", "dependencies": { - "@babel/traverse": "^7.13.15", - "@babel/types": "^7.13.16" + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz", - "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.5.tgz", + "integrity": "sha512-7fecSXq7ZrLE+TWshbGT+HyCLkxloWNhTbU2QM1NTI/tDqyf0oZiMcEfYtDuUDCo528EOlt39G1rftea4bRZIw==", "dependencies": { - "@babel/types": "^7.13.12" + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz", - "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz", + "integrity": "sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg==", "dependencies": { - "@babel/types": "^7.13.12" + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.13.14", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.13.14.tgz", - "integrity": "sha512-QuU/OJ0iAOSIatyVZmfqB0lbkVP0kDRiKj34xy+QNsnVZi/PA6BoSoreeqnxxa9EHFAIL0R9XOaAR/G9WlIy5g==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.5.tgz", + "integrity": "sha512-CkvMxgV4ZyyioElFwcuWnDCcNIeyqTkCm9BxXZi73RR1ozqlpboqsbGUNvRTflgZtFbbJ1v5Emvm+lkjMYY/LQ==", "dependencies": { - "@babel/helper-module-imports": "^7.13.12", - "@babel/helper-replace-supers": "^7.13.12", - "@babel/helper-simple-access": "^7.13.12", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/helper-validator-identifier": "^7.12.11", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.13.13", - "@babel/types": "^7.13.14" + "@babel/helper-environment-visitor": "^7.16.5", + "@babel/helper-module-imports": "^7.16.0", + "@babel/helper-simple-access": "^7.16.0", + "@babel/helper-split-export-declaration": "^7.16.0", + "@babel/helper-validator-identifier": "^7.15.7", + "@babel/template": "^7.16.0", + "@babel/traverse": "^7.16.5", + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", - "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.0.tgz", + "integrity": "sha512-SuI467Gi2V8fkofm2JPnZzB/SUuXoJA5zXe/xzyPP2M04686RzFKFHPK6HDVN6JvWBIEW8tt9hPR7fXdn2Lgpw==", "dependencies": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==" + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.5.tgz", + "integrity": "sha512-59KHWHXxVA9K4HNF4sbHCf+eJeFe0Te/ZFGqBT4OjXhrwvA04sGfaEGsVTdsjoszq0YTP49RC9UKe5g8uN2RwQ==", + "engines": { + "node": ">=6.9.0" + } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.13.0.tgz", - "integrity": "sha512-pUQpFBE9JvC9lrQbpX0TmeNIy5s7GnZjna2lhhcHC7DzgBs6fWn722Y5cfwgrtrqc7NAJwMvOa0mKhq6XaE4jg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.5.tgz", + "integrity": "sha512-X+aAJldyxrOmN9v3FKp+Hu1NO69VWgYgDGq6YDykwRPzxs5f2N+X988CBXS7EQahDU+Vpet5QYMqLk+nsp+Qxw==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.12.13", - "@babel/helper-wrap-function": "^7.13.0", - "@babel/types": "^7.13.0" + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-wrap-function": "^7.16.5", + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.13.12.tgz", - "integrity": "sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.5.tgz", + "integrity": "sha512-ao3seGVa/FZCMCCNDuBcqnBFSbdr8N2EW35mzojx3TwfIbdPmNK+JV6+2d5bR0Z71W5ocLnQp9en/cTF7pBJiQ==", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.13.12", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/traverse": "^7.13.0", - "@babel/types": "^7.13.12" + "@babel/helper-environment-visitor": "^7.16.5", + "@babel/helper-member-expression-to-functions": "^7.16.5", + "@babel/helper-optimise-call-expression": "^7.16.0", + "@babel/traverse": "^7.16.5", + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz", - "integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz", + "integrity": "sha512-o1rjBT/gppAqKsYfUdfHq5Rk03lMQrkPHG1OWzHWpLgVXRH4HnMM9Et9CVdIqwkCQlobnGHEJMsgWP/jE1zUiw==", "dependencies": { - "@babel/types": "^7.13.12" + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz", - "integrity": "sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", + "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==", "dependencies": { - "@babel/types": "^7.12.1" + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz", + "integrity": "sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw==", "dependencies": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", - "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==" + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "engines": { + "node": ">=6.9.0" + } }, "node_modules/@babel/helper-validator-option": { - "version": "7.12.17", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz", - "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==" + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", + "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", + "engines": { + "node": ">=6.9.0" + } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.13.0.tgz", - "integrity": "sha512-1UX9F7K3BS42fI6qd2A4BjKzgGjToscyZTdp1DjknHLCIvpgne6918io+aL5LXFcER/8QWiwpoY902pVEqgTXA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.5.tgz", + "integrity": "sha512-2J2pmLBqUqVdJw78U0KPNdeE2qeuIyKoG4mKV7wAq3mc4jJG282UgjZw4ZYDnqiWQuS3Y3IYdF/AQ6CpyBV3VA==", "dependencies": { - "@babel/helper-function-name": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.13.0", - "@babel/types": "^7.13.0" + "@babel/helper-function-name": "^7.16.0", + "@babel/template": "^7.16.0", + "@babel/traverse": "^7.16.5", + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.13.17", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.13.17.tgz", - "integrity": "sha512-Eal4Gce4kGijo1/TGJdqp3WuhllaMLSrW6XcL0ulyUAQOuxHcCafZE8KHg9857gcTehsm/v7RcOx2+jp0Ryjsg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.5.tgz", + "integrity": "sha512-TLgi6Lh71vvMZGEkFuIxzaPsyeYCHQ5jJOOX1f0xXn0uciFuE8cEk0wyBquMcCxBXZ5BJhE2aUB7pnWTD150Tw==", "dependencies": { - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.13.17", - "@babel/types": "^7.13.17" + "@babel/template": "^7.16.0", + "@babel/traverse": "^7.16.5", + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.13.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz", - "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", + "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", "dependencies": { - "@babel/helper-validator-identifier": "^7.12.11", + "@babel/helper-validator-identifier": "^7.15.7", "chalk": "^2.0.0", "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.16.tgz", - "integrity": "sha512-6bAg36mCwuqLO0hbR+z7PHuqWiCeP7Dzg73OpQwsAB1Eb8HnGEz5xYBzCfbu+YjoaJsJs+qheDxVAuqbt3ILEw==", + "version": "7.16.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.6.tgz", + "integrity": "sha512-Gr86ujcNuPDnNOY8mi383Hvi8IYrJVJYuf3XcuBM/Dgd+bINn/7tHqsj+tKkoreMbmGsFLsltI/JJd8fOFWGDQ==", "bin": { "parser": "bin/babel-parser.js" }, @@ -505,188 +602,277 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.16.2", + "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.16.2.tgz", + "integrity": "sha512-h37CvpLSf8gb2lIJ2CgC3t+EjFbi0t8qS7LCS1xcJIlEXE4czlofwaW7W1HA8zpgOCzI9C1nmoqNR1zWkk0pQg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.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.13.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.13.12.tgz", - "integrity": "sha512-d0u3zWKcoZf379fOeJdr1a5WPDny4aOFZ6hlfKivgK0LY7ZxNfoaHL2fWwdGtHyVvra38FC+HVYkO+byfSA8AQ==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.0.tgz", + "integrity": "sha512-4tcFwwicpWTrpl9qjf7UsoosaArgImF85AxqCRZlgc3IQDvkUHjJpruXAL58Wmj+T6fypWTC/BakfEkwIL/pwA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", - "@babel/plugin-proposal-optional-chaining": "^7.13.12" + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", + "@babel/plugin-proposal-optional-chaining": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.13.0" } }, "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.13.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.13.15.tgz", - "integrity": "sha512-VapibkWzFeoa6ubXy/NgV5U2U4MVnUlvnx6wo1XhlsaTrLYWE0UFpDQsVrmn22q5CzeloqJ8gEMHSKxuee6ZdA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.5.tgz", + "integrity": "sha512-C/FX+3HNLV6sz7AqbTQqEo1L9/kfrKjxcVtgyBCmvIgOjvuBVUWooDoi7trsLxOzCEo5FccjRvKHkfDsJFZlfA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-remap-async-to-generator": "^7.13.0", + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-remap-async-to-generator": "^7.16.5", "@babel/plugin-syntax-async-generators": "^7.8.4" }, + "engines": { + "node": ">=6.9.0" + }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.13.0.tgz", - "integrity": "sha512-KnTDjFNC1g+45ka0myZNvSBFLhNCLN+GeGYLDEA8Oq7MZ6yMgfLoIRh86GRT0FjtJhZw8JyUskP9uvj5pHM9Zg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.5.tgz", + "integrity": "sha512-pJD3HjgRv83s5dv1sTnDbZOaTjghKEz8KUn1Kbh2eAIRhGuyQ1XSeI4xVXU3UlIEVA3DAyIdxqT1eRn7Wcn55A==", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.13.0", - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-create-class-features-plugin": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-proposal-class-static-block": { + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.16.5.tgz", + "integrity": "sha512-EEFzuLZcm/rNJ8Q5krK+FRKdVkd6FjfzT9tuSZql9sQn64K0hHA2KLJ0DqVot9/iV6+SsuadC5yI39zWnm+nmQ==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.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-proposal-decorators": { - "version": "7.13.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.13.15.tgz", - "integrity": "sha512-ibAMAqUm97yzi+LPgdr5Nqb9CMkeieGHvwPg1ywSGjZrZHQEGqE01HmOio8kxRpA/+VtOHouIVy2FMpBbtltjA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.16.5.tgz", + "integrity": "sha512-XAiZll5oCdp2Dd2RbXA3LVPlFyIRhhcQy+G34p9ePpl6mjFkbqHAYHovyw2j5mqUrlBf0/+MtOIJ3JGYtz8qaw==", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.13.11", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-decorators": "^7.12.13" + "@babel/helper-create-class-features-plugin": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/plugin-syntax-decorators": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.13.8.tgz", - "integrity": "sha512-ONWKj0H6+wIRCkZi9zSbZtE/r73uOhMVHh256ys0UzfM7I3d4n+spZNWjOnJv2gzopumP2Wxi186vI8N0Y2JyQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.5.tgz", + "integrity": "sha512-P05/SJZTTvHz79LNYTF8ff5xXge0kk5sIIWAypcWgX4BTRUgyHc8wRxJ/Hk+mU0KXldgOOslKaeqnhthcDJCJQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-plugin-utils": "^7.16.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-proposal-export-namespace-from": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.13.tgz", - "integrity": "sha512-INAgtFo4OnLN3Y/j0VwAgw3HDXcDtX+C/erMvWzuV9v71r7urb6iyMXu7eM9IgLr1ElLlOkaHjJ0SbCmdOQ3Iw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.5.tgz", + "integrity": "sha512-i+sltzEShH1vsVydvNaTRsgvq2vZsfyrd7K7vPLUU/KgS0D5yZMe6uipM0+izminnkKrEfdUnz7CxMRb6oHZWw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13", + "@babel/helper-plugin-utils": "^7.16.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-proposal-json-strings": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.13.8.tgz", - "integrity": "sha512-w4zOPKUFPX1mgvTmL/fcEqy34hrQ1CRcGxdphBc6snDnnqJ47EZDIyop6IwXzAC8G916hsIuXB2ZMBCExC5k7Q==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.5.tgz", + "integrity": "sha512-QQJueTFa0y9E4qHANqIvMsuxM/qcLQmKttBACtPCQzGUEizsXDACGonlPiSwynHfOa3vNw0FPMVvQzbuXwh4SQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-plugin-utils": "^7.16.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-proposal-logical-assignment-operators": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.13.8.tgz", - "integrity": "sha512-aul6znYB4N4HGweImqKn59Su9RS8lbUIqxtXTOcAGtNIDczoEFv+l1EhmX8rUBp3G1jMjKJm8m0jXVp63ZpS4A==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.5.tgz", + "integrity": "sha512-xqibl7ISO2vjuQM+MzR3rkd0zfNWltk7n9QhaD8ghMmMceVguYrNDt7MikRyj4J4v3QehpnrU8RYLnC7z/gZLA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-plugin-utils": "^7.16.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-proposal-nullish-coalescing-operator": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.13.8.tgz", - "integrity": "sha512-iePlDPBn//UhxExyS9KyeYU7RM9WScAG+D3Hhno0PLJebAEpDZMocbDe64eqynhNAnwz/vZoL/q/QB2T1OH39A==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.5.tgz", + "integrity": "sha512-YwMsTp/oOviSBhrjwi0vzCUycseCYwoXnLiXIL3YNjHSMBHicGTz7GjVU/IGgz4DtOEXBdCNG72pvCX22ehfqg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-plugin-utils": "^7.16.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-proposal-numeric-separator": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.13.tgz", - "integrity": "sha512-O1jFia9R8BUCl3ZGB7eitaAPu62TXJRHn7rh+ojNERCFyqRwJMTmhz+tJ+k0CwI6CLjX/ee4qW74FSqlq9I35w==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.5.tgz", + "integrity": "sha512-DvB9l/TcsCRvsIV9v4jxR/jVP45cslTVC0PMVHvaJhhNuhn2Y1SOhCSFlPK777qLB5wb8rVDaNoqMTyOqtY5Iw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13", + "@babel/helper-plugin-utils": "^7.16.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-proposal-object-rest-spread": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.13.8.tgz", - "integrity": "sha512-DhB2EuB1Ih7S3/IRX5AFVgZ16k3EzfRbq97CxAVI1KSYcW+lexV8VZb7G7L8zuPVSdQMRn0kiBpf/Yzu9ZKH0g==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.16.5.tgz", + "integrity": "sha512-UEd6KpChoyPhCoE840KRHOlGhEZFutdPDMGj+0I56yuTTOaT51GzmnEl/0uT41fB/vD2nT+Pci2KjezyE3HmUw==", "dependencies": { - "@babel/compat-data": "^7.13.8", - "@babel/helper-compilation-targets": "^7.13.8", - "@babel/helper-plugin-utils": "^7.13.0", + "@babel/compat-data": "^7.16.4", + "@babel/helper-compilation-targets": "^7.16.3", + "@babel/helper-plugin-utils": "^7.16.5", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.13.0" + "@babel/plugin-transform-parameters": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.13.8.tgz", - "integrity": "sha512-0wS/4DUF1CuTmGo+NiaHfHcVSeSLj5S3e6RivPTg/2k3wOv3jO35tZ6/ZWsQhQMvdgI7CwphjQa/ccarLymHVA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.5.tgz", + "integrity": "sha512-ihCMxY1Iljmx4bWy/PIMJGXN4NS4oUj1MKynwO07kiKms23pNvIn1DMB92DNB2R0EA882sw0VXIelYGdtF7xEQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-plugin-utils": "^7.16.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-proposal-optional-chaining": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.13.12.tgz", - "integrity": "sha512-fcEdKOkIB7Tf4IxrgEVeFC4zeJSTr78no9wTdBuZZbqF64kzllU0ybo2zrzm7gUQfxGhBgq4E39oRs8Zx/RMYQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.5.tgz", + "integrity": "sha512-kzdHgnaXRonttiTfKYnSVafbWngPPr2qKw9BWYBESl91W54e+9R5pP70LtWxV56g0f05f/SQrwHYkfvbwcdQ/A==", "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", "@babel/plugin-syntax-optional-chaining": "^7.8.3" }, + "engines": { + "node": ">=6.9.0" + }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.13.0.tgz", - "integrity": "sha512-MXyyKQd9inhx1kDYPkFRVOBXQ20ES8Pto3T7UZ92xj2mY0EVD8oAVzeyYuVfy/mxAdTSIayOvg+aVzcHV2bn6Q==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.5.tgz", + "integrity": "sha512-+yFMO4BGT3sgzXo+lrq7orX5mAZt57DwUK6seqII6AcJnJOIhBJ8pzKH47/ql/d426uQ7YhN8DpUFirQzqYSUA==", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.13.0", - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-create-class-features-plugin": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.5.tgz", + "integrity": "sha512-+YGh5Wbw0NH3y/E5YMu6ci5qTDmAEVNoZ3I54aB6nVEOZ5BQ7QJlwKq5pYVucQilMByGn/bvX0af+uNaPRCabA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-create-class-features-plugin": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.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-proposal-unicode-property-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.13.tgz", - "integrity": "sha512-XyJmZidNfofEkqFV5VC/bLabGmO5QzenPO/YOfGuEbgU+2sSwMmio3YLb4WtBgcmmdwZHyVyv8on77IUjQ5Gvg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.5.tgz", + "integrity": "sha512-s5sKtlKQyFSatt781HQwv1hoM5BQ9qRH30r+dK56OLDsHmV74mzwJNX7R1yMuE7VZKG5O6q/gmOGSAO6ikTudg==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-create-regexp-features-plugin": "^7.16.0", + "@babel/helper-plugin-utils": "^7.16.5" }, "engines": { "node": ">=4" @@ -717,12 +903,29 @@ "@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-decorators": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.12.13.tgz", - "integrity": "sha512-Rw6aIXGuqDLr6/LoBBYE57nKOzQpz/aDkKlMqEwH+Vp0MXbG6H/TfRjaY343LKxzAKAMXIHsQ8JzaZKuDZ9MwA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.16.5.tgz", + "integrity": "sha512-3CbYTXfflvyy8O819uhZcZSMedZG4J8yS/NLTc/8T24M9ke1GssTGvg8VZu3Yn2LU5IyQSv1CmPq0a9JWHXJwg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" @@ -762,11 +965,14 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.13.tgz", - "integrity": "sha512-d4HM23Q1K7oq/SLNmG6mRt85l2csmQ0cHRaxRXjKW0YFdEXqlZ5kzFQKH5Uc3rDJECgu+yCRgPkG04Mm98R/1g==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.5.tgz", + "integrity": "sha512-42OGssv9NPk4QHKVgIHlzeLgPOW5rGgfV5jzG90AhcXXIv6hu/eqj63w4VgvRxdvZY3AlYeDgPiSJ3BqAd1Y6Q==", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" @@ -838,434 +1044,555 @@ "@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.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.13.tgz", - "integrity": "sha512-A81F9pDwyS7yM//KwbCSDqy3Uj4NMIurtplxphWxoYtNPov7cJsDkAFNNyVlIZ3jwGycVsurZ+LtOA8gZ376iQ==", + "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.12.13" + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.13.0.tgz", - "integrity": "sha512-96lgJagobeVmazXFaDrbmCLQxBysKu7U6Do3mLsx27gf5Dk85ezysrs2BZUpXD703U/Su1xTBDxxar2oa4jAGg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.5.tgz", + "integrity": "sha512-8bTHiiZyMOyfZFULjsCnYOWG059FVMes0iljEHSfARhNgFfpsqE92OrCffv3veSw9rwMkYcFe9bj0ZoXU2IGtQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-plugin-utils": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.13.0.tgz", - "integrity": "sha512-3j6E004Dx0K3eGmhxVJxwwI89CTJrce7lg3UrtFuDAVQ/2+SJ/h/aSFOeE6/n0WB1GsOffsJp6MnPQNQ8nmwhg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.5.tgz", + "integrity": "sha512-TMXgfioJnkXU+XRoj7P2ED7rUm5jbnDWwlCuFVTpQboMfbSya5WrmubNBAMlk7KXvywpo8rd8WuYZkis1o2H8w==", "dependencies": { - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-remap-async-to-generator": "^7.13.0" + "@babel/helper-module-imports": "^7.16.0", + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-remap-async-to-generator": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.13.tgz", - "integrity": "sha512-zNyFqbc3kI/fVpqwfqkg6RvBgFpC4J18aKKMmv7KdQ/1GgREapSJAykLMVNwfRGO3BtHj3YQZl8kxCXPcVMVeg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.5.tgz", + "integrity": "sha512-BxmIyKLjUGksJ99+hJyL/HIxLIGnLKtw772zYDER7UuycDZ+Xvzs98ZQw6NGgM2ss4/hlFAaGiZmMNKvValEjw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.13.16.tgz", - "integrity": "sha512-ad3PHUxGnfWF4Efd3qFuznEtZKoBp0spS+DgqzVzRPV7urEBvPLue3y2j80w4Jf2YLzZHj8TOv/Lmvdmh3b2xg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.5.tgz", + "integrity": "sha512-JxjSPNZSiOtmxjX7PBRBeRJTUKTyJ607YUYeT0QJCNdsedOe+/rXITjP08eG8xUpsLfPirgzdCFN+h0w6RI+pQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-plugin-utils": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.13.0.tgz", - "integrity": "sha512-9BtHCPUARyVH1oXGcSJD3YpsqRLROJx5ZNP6tN5vnk17N0SVf9WCtf8Nuh1CFmgByKKAIMstitKduoCmsaDK5g==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.12.13", - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-replace-supers": "^7.13.0", - "@babel/helper-split-export-declaration": "^7.12.13", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.5.tgz", + "integrity": "sha512-DzJ1vYf/7TaCYy57J3SJ9rV+JEuvmlnvvyvYKFbk5u46oQbBvuB9/0w+YsVsxkOv8zVWKpDmUoj4T5ILHoXevA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-environment-visitor": "^7.16.5", + "@babel/helper-function-name": "^7.16.0", + "@babel/helper-optimise-call-expression": "^7.16.0", + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-replace-supers": "^7.16.5", + "@babel/helper-split-export-declaration": "^7.16.0", "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.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.13.0.tgz", - "integrity": "sha512-RRqTYTeZkZAz8WbieLTvKUEUxZlUTdmL5KGMyZj7FnMfLNKV4+r5549aORG/mgojRmFlQMJDUupwAMiF2Q7OUg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.5.tgz", + "integrity": "sha512-n1+O7xtU5lSLraRzX88CNcpl7vtGdPakKzww74bVwpAIRgz9JVLJJpOLb0uYqcOaXVM0TL6X0RVeIJGD2CnCkg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-plugin-utils": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.13.17", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.13.17.tgz", - "integrity": "sha512-UAUqiLv+uRLO+xuBKKMEpC+t7YRNVRqBsWWq1yKXbBZBje/t3IXCiSinZhjn/DC3qzBfICeYd2EFGEbHsh5RLA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.16.5.tgz", + "integrity": "sha512-GuRVAsjq+c9YPK6NeTkRLWyQskDC099XkBSVO+6QzbnOnH2d/4mBVXYStaPrZD3dFRfg00I6BFJ9Atsjfs8mlg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-plugin-utils": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.13.tgz", - "integrity": "sha512-foDrozE65ZFdUC2OfgeOCrEPTxdB3yjqxpXh8CH+ipd9CHd4s/iq81kcUpyH8ACGNEPdFqbtzfgzbT/ZGlbDeQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.5.tgz", + "integrity": "sha512-iQiEMt8Q4/5aRGHpGVK2Zc7a6mx7qEAO7qehgSug3SDImnuMzgmm/wtJALXaz25zUj1PmnNHtShjFgk4PDx4nw==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-create-regexp-features-plugin": "^7.16.0", + "@babel/helper-plugin-utils": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.13.tgz", - "integrity": "sha512-NfADJiiHdhLBW3pulJlJI2NB0t4cci4WTZ8FtdIuNc2+8pslXdPtRRAEWqUY+m9kNOk2eRYbTAOipAxlrOcwwQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.5.tgz", + "integrity": "sha512-81tijpDg2a6I1Yhj4aWY1l3O1J4Cg/Pd7LfvuaH2VVInAkXtzibz9+zSPdUM1WvuUi128ksstAP0hM5w48vQgg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.13.tgz", - "integrity": "sha512-fbUelkM1apvqez/yYx1/oICVnGo2KM5s63mhGylrmXUxK/IAXSIf87QIxVfZldWf4QsOafY6vV3bX8aMHSvNrA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.5.tgz", + "integrity": "sha512-12rba2HwemQPa7BLIKCzm1pT2/RuQHtSFHdNl41cFiC6oi4tcrp7gjB07pxQvFpcADojQywSjblQth6gJyE6CA==", "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.13.0.tgz", - "integrity": "sha512-IHKT00mwUVYE0zzbkDgNRP6SRzvfGCYsOxIRz8KsiaaHCcT9BWIkO+H9QRJseHBLOGBZkHUdHiqj6r0POsdytg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.5.tgz", + "integrity": "sha512-+DpCAJFPAvViR17PIMi9x2AE34dll5wNlXO43wagAX2YcRGgEVHCNFC4azG85b4YyyFarvkc/iD5NPrz4Oneqw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-plugin-utils": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.13.tgz", - "integrity": "sha512-6K7gZycG0cmIwwF7uMK/ZqeCikCGVBdyP2J5SKNCXO5EOHcqi+z7Jwf8AmyDNcBgxET8DrEtCt/mPKPyAzXyqQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.5.tgz", + "integrity": "sha512-Fuec/KPSpVLbGo6z1RPw4EE1X+z9gZk1uQmnYy7v4xr4TO9p41v1AoUuXEtyqAI7H+xNJYSICzRqZBhDEkd3kQ==", "dependencies": { - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-function-name": "^7.16.0", + "@babel/helper-plugin-utils": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.13.tgz", - "integrity": "sha512-FW+WPjSR7hiUxMcKqyNjP05tQ2kmBCdpEpZHY1ARm96tGQCCBvXKnpjILtDplUnJ/eHZ0lALLM+d2lMFSpYJrQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.5.tgz", + "integrity": "sha512-B1j9C/IfvshnPcklsc93AVLTrNVa69iSqztylZH6qnmiAsDDOmmjEYqOm3Ts2lGSgTSywnBNiqC949VdD0/gfw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.13.tgz", - "integrity": "sha512-kxLkOsg8yir4YeEPHLuO2tXP9R/gTjpuTOjshqSpELUN3ZAg2jfDnKUvzzJxObun38sw3wm4Uu69sX/zA7iRvg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.5.tgz", + "integrity": "sha512-d57i3vPHWgIde/9Y8W/xSFUndhvhZN5Wu2TjRrN1MVz5KzdUihKnfDVlfP1U7mS5DNj/WHHhaE4/tTi4hIyHwQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.13.0.tgz", - "integrity": "sha512-EKy/E2NHhY/6Vw5d1k3rgoobftcNUmp9fGjb9XZwQLtTctsRBOTRO7RHHxfIky1ogMN5BxN7p9uMA3SzPfotMQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.5.tgz", + "integrity": "sha512-oHI15S/hdJuSCfnwIz+4lm6wu/wBn7oJ8+QrkzPPwSFGXk8kgdI/AIKcbR/XnD1nQVMg/i6eNaXpszbGuwYDRQ==", "dependencies": { - "@babel/helper-module-transforms": "^7.13.0", - "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-module-transforms": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5", "babel-plugin-dynamic-import-node": "^2.3.3" }, + "engines": { + "node": ">=6.9.0" + }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.13.8.tgz", - "integrity": "sha512-9QiOx4MEGglfYZ4XOnU79OHr6vIWUakIj9b4mioN8eQIoEh+pf5p/zEB36JpDFWA12nNMiRf7bfoRvl9Rn79Bw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.5.tgz", + "integrity": "sha512-ABhUkxvoQyqhCWyb8xXtfwqNMJD7tx+irIRnUh6lmyFud7Jln1WzONXKlax1fg/ey178EXbs4bSGNd6PngO+SQ==", "dependencies": { - "@babel/helper-module-transforms": "^7.13.0", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-simple-access": "^7.12.13", + "@babel/helper-module-transforms": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-simple-access": "^7.16.0", "babel-plugin-dynamic-import-node": "^2.3.3" }, + "engines": { + "node": ">=6.9.0" + }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.13.8.tgz", - "integrity": "sha512-hwqctPYjhM6cWvVIlOIe27jCIBgHCsdH2xCJVAYQm7V5yTMoilbVMi9f6wKg0rpQAOn6ZG4AOyvCqFF/hUh6+A==", - "dependencies": { - "@babel/helper-hoist-variables": "^7.13.0", - "@babel/helper-module-transforms": "^7.13.0", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-validator-identifier": "^7.12.11", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.16.5.tgz", + "integrity": "sha512-53gmLdScNN28XpjEVIm7LbWnD/b/TpbwKbLk6KV4KqC9WyU6rq1jnNmVG6UgAdQZVVGZVoik3DqHNxk4/EvrjA==", + "dependencies": { + "@babel/helper-hoist-variables": "^7.16.0", + "@babel/helper-module-transforms": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-validator-identifier": "^7.15.7", "babel-plugin-dynamic-import-node": "^2.3.3" }, + "engines": { + "node": ">=6.9.0" + }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.13.0.tgz", - "integrity": "sha512-D/ILzAh6uyvkWjKKyFE/W0FzWwasv6vPTSqPcjxFqn6QpX3u8DjRVliq4F2BamO2Wee/om06Vyy+vPkNrd4wxw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.5.tgz", + "integrity": "sha512-qTFnpxHMoenNHkS3VoWRdwrcJ3FhX567GvDA3hRZKF0Dj8Fmg0UzySZp3AP2mShl/bzcywb/UWAMQIjA1bhXvw==", "dependencies": { - "@babel/helper-module-transforms": "^7.13.0", - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-module-transforms": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.13.tgz", - "integrity": "sha512-Xsm8P2hr5hAxyYblrfACXpQKdQbx4m2df9/ZZSQ8MAhsadw06+jW7s9zsSw6he+mJZXRlVMyEnVktJo4zjk1WA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.5.tgz", + "integrity": "sha512-/wqGDgvFUeKELW6ex6QB7dLVRkd5ehjw34tpXu1nhKC0sFfmaLabIswnpf8JgDyV2NeDmZiwoOb0rAmxciNfjA==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.12.13" + "@babel/helper-create-regexp-features-plugin": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.13.tgz", - "integrity": "sha512-/KY2hbLxrG5GTQ9zzZSc3xWiOy379pIETEhbtzwZcw9rvuaVV4Fqy7BYGYOWZnaoXIQYbbJ0ziXLa/sKcGCYEQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.5.tgz", + "integrity": "sha512-ZaIrnXF08ZC8jnKR4/5g7YakGVL6go6V9ql6Jl3ecO8PQaQqFE74CuM384kezju7Z9nGCCA20BqZaR1tJ/WvHg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.13.tgz", - "integrity": "sha512-JzYIcj3XtYspZDV8j9ulnoMPZZnF/Cj0LUxPOjR89BdBVx+zYJI9MdMIlUZjbXDX+6YVeS6I3e8op+qQ3BYBoQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.5.tgz", + "integrity": "sha512-tded+yZEXuxt9Jdtkc1RraW1zMF/GalVxaVVxh41IYwirdRgyAxxxCKZ9XB7LxZqmsjfjALxupNE1MIz9KH+Zg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13", - "@babel/helper-replace-supers": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-replace-supers": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.13.0.tgz", - "integrity": "sha512-Jt8k/h/mIwE2JFEOb3lURoY5C85ETcYPnbuAJ96zRBzh1XHtQZfs62ChZ6EP22QlC8c7Xqr9q+e1SU5qttwwjw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.5.tgz", + "integrity": "sha512-B3O6AL5oPop1jAVg8CV+haeUte9oFuY85zu0jwnRNZZi3tVAbJriu5tag/oaO2kGaQM/7q7aGPBlTI5/sr9enA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-plugin-utils": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.13.tgz", - "integrity": "sha512-nqVigwVan+lR+g8Fj8Exl0UQX2kymtjcWfMOYM1vTYEKujeyv2SkMgazf2qNcK7l4SDiKyTA/nHCPqL4e2zo1A==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.5.tgz", + "integrity": "sha512-+IRcVW71VdF9pEH/2R/Apab4a19LVvdVsr/gEeotH00vSDVlKD+XgfSIw+cgGWsjDB/ziqGv/pGoQZBIiQVXHg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.13.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.13.15.tgz", - "integrity": "sha512-Bk9cOLSz8DiurcMETZ8E2YtIVJbFCPGW28DJWUakmyVWtQSm6Wsf0p4B4BfEr/eL2Nkhe/CICiUiMOCi1TPhuQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.5.tgz", + "integrity": "sha512-2z+it2eVWU8TtQQRauvGUqZwLy4+7rTfo6wO4npr+fvvN1SW30ZF3O/ZRCNmTuu4F5MIP8OJhXAhRV5QMJOuYg==", "dependencies": { "regenerator-transform": "^0.14.2" }, + "engines": { + "node": ">=6.9.0" + }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.13.tgz", - "integrity": "sha512-xhUPzDXxZN1QfiOy/I5tyye+TRz6lA7z6xaT4CLOjPRMVg1ldRf0LHw0TDBpYL4vG78556WuHdyO9oi5UmzZBg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.5.tgz", + "integrity": "sha512-aIB16u8lNcf7drkhXJRoggOxSTUAuihTSTfAcpynowGJOZiGf+Yvi7RuTwFzVYSYPmWyARsPqUGoZWWWxLiknw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.13.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.13.15.tgz", - "integrity": "sha512-d+ezl76gx6Jal08XngJUkXM4lFXK/5Ikl9Mh4HKDxSfGJXmZ9xG64XT2oivBzfxb/eQ62VfvoMkaCZUKJMVrBA==", - "dependencies": { - "@babel/helper-module-imports": "^7.13.12", - "@babel/helper-plugin-utils": "^7.13.0", - "babel-plugin-polyfill-corejs2": "^0.2.0", - "babel-plugin-polyfill-corejs3": "^0.2.0", - "babel-plugin-polyfill-regenerator": "^0.2.0", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.16.5.tgz", + "integrity": "sha512-gxpfS8XQWDbQ8oP5NcmpXxtEgCJkbO+W9VhZlOhr0xPyVaRjAQPOv7ZDj9fg0d5s9+NiVvMCE6gbkEkcsxwGRw==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.0", + "@babel/helper-plugin-utils": "^7.16.5", + "babel-plugin-polyfill-corejs2": "^0.3.0", + "babel-plugin-polyfill-corejs3": "^0.4.0", + "babel-plugin-polyfill-regenerator": "^0.3.0", "semver": "^6.3.0" }, + "engines": { + "node": ">=6.9.0" + }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.13.tgz", - "integrity": "sha512-xpL49pqPnLtf0tVluuqvzWIgLEhuPpZzvs2yabUHSKRNlN7ScYU7aMlmavOeyXJZKgZKQRBlh8rHbKiJDraTSw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.5.tgz", + "integrity": "sha512-ZbuWVcY+MAXJuuW7qDoCwoxDUNClfZxoo7/4swVbOW1s/qYLOMHlm9YRWMsxMFuLs44eXsv4op1vAaBaBaDMVg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.13.0.tgz", - "integrity": "sha512-V6vkiXijjzYeFmQTr3dBxPtZYLPcUfY34DebOU27jIl2M/Y8Egm52Hw82CSjjPqd54GTlJs5x+CR7HeNr24ckg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.5.tgz", + "integrity": "sha512-5d6l/cnG7Lw4tGHEoga4xSkYp1euP7LAtrah1h1PgJ3JY7yNsjybsxQAnVK4JbtReZ/8z6ASVmd3QhYYKLaKZw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1" + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.13.tgz", - "integrity": "sha512-Jc3JSaaWT8+fr7GRvQP02fKDsYk4K/lYwWq38r/UGfaxo89ajud321NH28KRQ7xy1Ybc0VUE5Pz8psjNNDUglg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.5.tgz", + "integrity": "sha512-usYsuO1ID2LXxzuUxifgWtJemP7wL2uZtyrTVM4PKqsmJycdS4U4mGovL5xXkfUheds10Dd2PjoQLXw6zCsCbg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.13.0.tgz", - "integrity": "sha512-d67umW6nlfmr1iehCcBv69eSUSySk1EsIS8aTDX4Xo9qajAh6mYtcl4kJrBkGXuxZPEgVr7RVfAvNW6YQkd4Mw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.5.tgz", + "integrity": "sha512-gnyKy9RyFhkovex4BjKWL3BVYzUDG6zC0gba7VMLbQoDuqMfJ1SDXs8k/XK41Mmt1Hyp4qNAvGFb9hKzdCqBRQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-plugin-utils": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.13.tgz", - "integrity": "sha512-eKv/LmUJpMnu4npgfvs3LiHhJua5fo/CysENxa45YCQXZwKnGCQKAg87bvoqSW1fFT+HA32l03Qxsm8ouTY3ZQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.5.tgz", + "integrity": "sha512-ldxCkW180qbrvyCVDzAUZqB0TAeF8W/vGJoRcaf75awm6By+PxfJKvuqVAnq8N9wz5Xa6mSpM19OfVKKVmGHSQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.13.tgz", - "integrity": "sha512-0bHEkdwJ/sN/ikBHfSmOXPypN/beiGqjo+o4/5K+vxEFNPRPdImhviPakMKG4x96l85emoa0Z6cDflsdBusZbw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.5.tgz", + "integrity": "sha512-shiCBHTIIChGLdyojsKQjoAyB8MBwat25lKM7MJjbe1hE0bgIppD+LX9afr41lLHOhqceqeWl4FkLp+Bgn9o1Q==", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.13.tgz", - "integrity": "sha512-mDRzSNY7/zopwisPZ5kM9XKCfhchqIYwAKRERtEnhYscZB79VRekuRSoYbN0+KVe3y8+q1h6A4svXtP7N+UoCA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.5.tgz", + "integrity": "sha512-GTJ4IW012tiPEMMubd7sD07iU9O/LOo8Q/oU4xNhcaq0Xn8+6TcUQaHtC8YxySo1T+ErQ8RaWogIEeFhKGNPzw==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-create-regexp-features-plugin": "^7.16.0", + "@babel/helper-plugin-utils": "^7.16.5" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/preset-env": { - "version": "7.13.15", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.13.15.tgz", - "integrity": "sha512-D4JAPMXcxk69PKe81jRJ21/fP/uYdcTZ3hJDF5QX2HSI9bBxxYw/dumdR6dGumhjxlprHPE4XWoPaqzZUVy2MA==", - "dependencies": { - "@babel/compat-data": "^7.13.15", - "@babel/helper-compilation-targets": "^7.13.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-validator-option": "^7.12.17", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.13.12", - "@babel/plugin-proposal-async-generator-functions": "^7.13.15", - "@babel/plugin-proposal-class-properties": "^7.13.0", - "@babel/plugin-proposal-dynamic-import": "^7.13.8", - "@babel/plugin-proposal-export-namespace-from": "^7.12.13", - "@babel/plugin-proposal-json-strings": "^7.13.8", - "@babel/plugin-proposal-logical-assignment-operators": "^7.13.8", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", - "@babel/plugin-proposal-numeric-separator": "^7.12.13", - "@babel/plugin-proposal-object-rest-spread": "^7.13.8", - "@babel/plugin-proposal-optional-catch-binding": "^7.13.8", - "@babel/plugin-proposal-optional-chaining": "^7.13.12", - "@babel/plugin-proposal-private-methods": "^7.13.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.12.13", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.16.5.tgz", + "integrity": "sha512-MiJJW5pwsktG61NDxpZ4oJ1CKxM1ncam9bzRtx9g40/WkLRkxFP6mhpkYV0/DxcciqoiHicx291+eUQrXb/SfQ==", + "dependencies": { + "@babel/compat-data": "^7.16.4", + "@babel/helper-compilation-targets": "^7.16.3", + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-validator-option": "^7.14.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.16.2", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.16.0", + "@babel/plugin-proposal-async-generator-functions": "^7.16.5", + "@babel/plugin-proposal-class-properties": "^7.16.5", + "@babel/plugin-proposal-class-static-block": "^7.16.5", + "@babel/plugin-proposal-dynamic-import": "^7.16.5", + "@babel/plugin-proposal-export-namespace-from": "^7.16.5", + "@babel/plugin-proposal-json-strings": "^7.16.5", + "@babel/plugin-proposal-logical-assignment-operators": "^7.16.5", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.5", + "@babel/plugin-proposal-numeric-separator": "^7.16.5", + "@babel/plugin-proposal-object-rest-spread": "^7.16.5", + "@babel/plugin-proposal-optional-catch-binding": "^7.16.5", + "@babel/plugin-proposal-optional-chaining": "^7.16.5", + "@babel/plugin-proposal-private-methods": "^7.16.5", + "@babel/plugin-proposal-private-property-in-object": "^7.16.5", + "@babel/plugin-proposal-unicode-property-regex": "^7.16.5", "@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-json-strings": "^7.8.3", @@ -1275,55 +1602,59 @@ "@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-top-level-await": "^7.12.13", - "@babel/plugin-transform-arrow-functions": "^7.13.0", - "@babel/plugin-transform-async-to-generator": "^7.13.0", - "@babel/plugin-transform-block-scoped-functions": "^7.12.13", - "@babel/plugin-transform-block-scoping": "^7.12.13", - "@babel/plugin-transform-classes": "^7.13.0", - "@babel/plugin-transform-computed-properties": "^7.13.0", - "@babel/plugin-transform-destructuring": "^7.13.0", - "@babel/plugin-transform-dotall-regex": "^7.12.13", - "@babel/plugin-transform-duplicate-keys": "^7.12.13", - "@babel/plugin-transform-exponentiation-operator": "^7.12.13", - "@babel/plugin-transform-for-of": "^7.13.0", - "@babel/plugin-transform-function-name": "^7.12.13", - "@babel/plugin-transform-literals": "^7.12.13", - "@babel/plugin-transform-member-expression-literals": "^7.12.13", - "@babel/plugin-transform-modules-amd": "^7.13.0", - "@babel/plugin-transform-modules-commonjs": "^7.13.8", - "@babel/plugin-transform-modules-systemjs": "^7.13.8", - "@babel/plugin-transform-modules-umd": "^7.13.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.12.13", - "@babel/plugin-transform-new-target": "^7.12.13", - "@babel/plugin-transform-object-super": "^7.12.13", - "@babel/plugin-transform-parameters": "^7.13.0", - "@babel/plugin-transform-property-literals": "^7.12.13", - "@babel/plugin-transform-regenerator": "^7.13.15", - "@babel/plugin-transform-reserved-words": "^7.12.13", - "@babel/plugin-transform-shorthand-properties": "^7.12.13", - "@babel/plugin-transform-spread": "^7.13.0", - "@babel/plugin-transform-sticky-regex": "^7.12.13", - "@babel/plugin-transform-template-literals": "^7.13.0", - "@babel/plugin-transform-typeof-symbol": "^7.12.13", - "@babel/plugin-transform-unicode-escapes": "^7.12.13", - "@babel/plugin-transform-unicode-regex": "^7.12.13", - "@babel/preset-modules": "^0.1.4", - "@babel/types": "^7.13.14", - "babel-plugin-polyfill-corejs2": "^0.2.0", - "babel-plugin-polyfill-corejs3": "^0.2.0", - "babel-plugin-polyfill-regenerator": "^0.2.0", - "core-js-compat": "^3.9.0", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.16.5", + "@babel/plugin-transform-async-to-generator": "^7.16.5", + "@babel/plugin-transform-block-scoped-functions": "^7.16.5", + "@babel/plugin-transform-block-scoping": "^7.16.5", + "@babel/plugin-transform-classes": "^7.16.5", + "@babel/plugin-transform-computed-properties": "^7.16.5", + "@babel/plugin-transform-destructuring": "^7.16.5", + "@babel/plugin-transform-dotall-regex": "^7.16.5", + "@babel/plugin-transform-duplicate-keys": "^7.16.5", + "@babel/plugin-transform-exponentiation-operator": "^7.16.5", + "@babel/plugin-transform-for-of": "^7.16.5", + "@babel/plugin-transform-function-name": "^7.16.5", + "@babel/plugin-transform-literals": "^7.16.5", + "@babel/plugin-transform-member-expression-literals": "^7.16.5", + "@babel/plugin-transform-modules-amd": "^7.16.5", + "@babel/plugin-transform-modules-commonjs": "^7.16.5", + "@babel/plugin-transform-modules-systemjs": "^7.16.5", + "@babel/plugin-transform-modules-umd": "^7.16.5", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.16.5", + "@babel/plugin-transform-new-target": "^7.16.5", + "@babel/plugin-transform-object-super": "^7.16.5", + "@babel/plugin-transform-parameters": "^7.16.5", + "@babel/plugin-transform-property-literals": "^7.16.5", + "@babel/plugin-transform-regenerator": "^7.16.5", + "@babel/plugin-transform-reserved-words": "^7.16.5", + "@babel/plugin-transform-shorthand-properties": "^7.16.5", + "@babel/plugin-transform-spread": "^7.16.5", + "@babel/plugin-transform-sticky-regex": "^7.16.5", + "@babel/plugin-transform-template-literals": "^7.16.5", + "@babel/plugin-transform-typeof-symbol": "^7.16.5", + "@babel/plugin-transform-unicode-escapes": "^7.16.5", + "@babel/plugin-transform-unicode-regex": "^7.16.5", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.16.0", + "babel-plugin-polyfill-corejs2": "^0.3.0", + "babel-plugin-polyfill-corejs3": "^0.4.0", + "babel-plugin-polyfill-regenerator": "^0.3.0", + "core-js-compat": "^3.19.1", "semver": "^6.3.0" }, + "engines": { + "node": ">=6.9.0" + }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/preset-modules": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", - "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", @@ -1336,42 +1667,53 @@ } }, "node_modules/@babel/runtime": { - "version": "7.13.17", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.17.tgz", - "integrity": "sha512-NCdgJEelPTSh+FEFylhnP1ylq848l1z9t9N0j1Lfbcw0+KXGjsTvUmkxy+voLLXB5SOKMbLLx4jxYliGrYQseA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz", + "integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==", "dependencies": { "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz", + "integrity": "sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==", "dependencies": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/code-frame": "^7.16.0", + "@babel/parser": "^7.16.0", + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.13.17", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.17.tgz", - "integrity": "sha512-BMnZn0R+X6ayqm3C3To7o1j7Q020gWdqdyP50KEoVqaCO2c/Im7sYZSmVgvefp8TTMQ+9CtwuBp0Z1CZ8V3Pvg==", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.13.16", - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.13.16", - "@babel/types": "^7.13.17", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.5.tgz", + "integrity": "sha512-FOCODAzqUMROikDYLYxl4nmwiLlu85rNqBML/A5hKRVXG2LV8d0iMqgPzdYTcIpjZEBB7D6UDU9vxRZiriASdQ==", + "dependencies": { + "@babel/code-frame": "^7.16.0", + "@babel/generator": "^7.16.5", + "@babel/helper-environment-visitor": "^7.16.5", + "@babel/helper-function-name": "^7.16.0", + "@babel/helper-hoist-variables": "^7.16.0", + "@babel/helper-split-export-declaration": "^7.16.0", + "@babel/parser": "^7.16.5", + "@babel/types": "^7.16.0", "debug": "^4.1.0", "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/traverse/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dependencies": { "ms": "2.1.2" }, @@ -1390,12 +1732,15 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/@babel/types": { - "version": "7.13.17", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.17.tgz", - "integrity": "sha512-RawydLgxbOPDlTLJNtoIypwdmAy//uQIzlKt2+iBiJaRlVuI6QLUxVAyWGNfOzp8Yu4L4lLIacoCyTNtpb4wiA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", + "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", "dependencies": { - "@babel/helper-validator-identifier": "^7.12.11", + "@babel/helper-validator-identifier": "^7.15.7", "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@cosmos-ui/vue": { @@ -1513,33 +1858,33 @@ } }, "node_modules/@types/glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", "dependencies": { "@types/minimatch": "*", "@types/node": "*" } }, "node_modules/@types/json-schema": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", - "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==" + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==" }, "node_modules/@types/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==" + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==" }, "node_modules/@types/node": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.1.tgz", - "integrity": "sha512-TMkXt0Ck1y0KKsGr9gJtWGjttxlZnnvDtphxUOSd0bfaR6Q1jle+sPvrzNR1urqYTWMinoKvjKfXUGsumaO1PA==" + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.2.tgz", + "integrity": "sha512-JepeIUPFDARgIs0zD/SKPgFsJEAF0X5/qO80llx59gOxFTboS9Amv3S+QfB7lqBId5sFXJ99BN0J6zFRvL9dDA==" }, "node_modules/@types/q": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", - "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==" + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", + "integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==" }, "node_modules/@vue/babel-helper-vue-jsx-merge-props": { "version": "1.2.1", @@ -1552,9 +1897,9 @@ "integrity": "sha512-hz4R8tS5jMn8lDq6iD+yWL6XNB699pGIVLk7WSJnn1dbpjaazsjZQkieJoRX6gW5zpYSCFqQ7jUquPNY65tQYA==" }, "node_modules/@vue/babel-plugin-jsx": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.0.5.tgz", - "integrity": "sha512-Jtipy7oI0am5e1q5Ahunm/cCcCh5ssf5VkMQsLR383S3un5Qh7NBfxgSK9kmWf4IXJEhDeYp9kHv8G/EnMai9A==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.1.1.tgz", + "integrity": "sha512-j2uVfZjnB5+zkcbc/zsOc0fSNGCMMjaEXP52wdwdIfn0qjFfEYpYZBFKFg+HHnQeJCVrjOeO0YxgaL7DMrym9w==", "dependencies": { "@babel/helper-module-imports": "^7.0.0", "@babel/plugin-syntax-jsx": "^7.0.0", @@ -1592,9 +1937,9 @@ } }, "node_modules/@vue/babel-preset-app": { - "version": "4.5.12", - "resolved": "https://registry.npmjs.org/@vue/babel-preset-app/-/babel-preset-app-4.5.12.tgz", - "integrity": "sha512-8q67ORQ9O0Ms0nlqsXTVhaBefRBaLrzPxOewAZhdcO7onHwcO5/wRdWtHhZgfpCZlhY7NogkU16z3WnorSSkEA==", + "version": "4.5.15", + "resolved": "https://registry.npmjs.org/@vue/babel-preset-app/-/babel-preset-app-4.5.15.tgz", + "integrity": "sha512-J+YttzvwRfV1BPczf8r3qCevznYk+jh531agVF+5EYlHF4Sgh/cGXTz9qkkiux3LQgvhEGXgmCteg1n38WuuKg==", "dependencies": { "@babel/core": "^7.11.0", "@babel/helper-compilation-targets": "^7.9.6", @@ -1743,21 +2088,21 @@ } }, "node_modules/@vue/component-compiler-utils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.2.0.tgz", - "integrity": "sha512-lejBLa7xAMsfiZfNp7Kv51zOzifnb29FwdnMLa96z26kXErPFioSf9BMcePVIQ6/Gc6/mC0UrPpxAWIHyae0vw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.3.0.tgz", + "integrity": "sha512-97sfH2mYNU+2PzGrmK2haqffDpVASuib9/w2/noxiFi31Z54hW+q3izKQXXQZSNhtiUpAI36uSuYepeBe4wpHQ==", "dependencies": { "consolidate": "^0.15.1", "hash-sum": "^1.0.2", "lru-cache": "^4.1.2", "merge-source-map": "^1.1.0", - "postcss": "^7.0.14", + "postcss": "^7.0.36", "postcss-selector-parser": "^6.0.2", "source-map": "~0.6.1", "vue-template-es2015-compiler": "^1.9.0" }, "optionalDependencies": { - "prettier": "^1.18.2" + "prettier": "^1.18.2 || ^2.0.0" } }, "node_modules/@vue/component-compiler-utils/node_modules/lru-cache": { @@ -1783,17 +2128,17 @@ "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" }, "node_modules/@vuepress/core": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/core/-/core-1.8.2.tgz", - "integrity": "sha512-lh9BLC06k9s0wxTuWtCkiNj49fkbW87enp0XSrFZHEoyDGSGndQjZmMMErcHc5Hx7nrW1nzc33sPH1NNtJl0hw==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@vuepress/core/-/core-1.8.3.tgz", + "integrity": "sha512-VY5yfePXt5nf0/wf6RLPXheRrSwXBizW4aYFHE6L59gzwFAJc01iYJ6eW+LDiyX6lb45pWuyZHMdNsMzBgs9xA==", "dependencies": { "@babel/core": "^7.8.4", "@vue/babel-preset-app": "^4.1.2", - "@vuepress/markdown": "1.8.2", - "@vuepress/markdown-loader": "1.8.2", - "@vuepress/plugin-last-updated": "1.8.2", - "@vuepress/plugin-register-components": "1.8.2", - "@vuepress/shared-utils": "1.8.2", + "@vuepress/markdown": "1.8.3", + "@vuepress/markdown-loader": "1.8.3", + "@vuepress/plugin-last-updated": "1.8.3", + "@vuepress/plugin-register-components": "1.8.3", + "@vuepress/shared-utils": "1.8.3", "autoprefixer": "^9.5.1", "babel-loader": "^8.0.4", "cache-loader": "^3.0.0", @@ -1831,11 +2176,11 @@ } }, "node_modules/@vuepress/markdown": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/markdown/-/markdown-1.8.2.tgz", - "integrity": "sha512-zznBHVqW+iBkznF/BO/GY9RFu53khyl0Ey0PnGqvwCJpRLNan6y5EXgYumtjw2GSYn5nDTTALYxtyNBdz64PKg==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@vuepress/markdown/-/markdown-1.8.3.tgz", + "integrity": "sha512-gYfMulsHbCS5o/YBoMd6uZDlbiABewxAQq7J71REz7xpWbk1XjPzxYD40VT4xrEYllc/HdQQe49C+34eKHPhMw==", "dependencies": { - "@vuepress/shared-utils": "1.8.2", + "@vuepress/shared-utils": "1.8.3", "markdown-it": "^8.4.1", "markdown-it-anchor": "^5.0.2", "markdown-it-chain": "^1.3.0", @@ -1845,11 +2190,11 @@ } }, "node_modules/@vuepress/markdown-loader": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/markdown-loader/-/markdown-loader-1.8.2.tgz", - "integrity": "sha512-mWzFXikCUcAN/chpKkqZpRYKdo0312hMv8cBea2hvrJYV6y4ODB066XKvXN8JwOcxuCjxWYJkhWGr+pXq1oTtw==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@vuepress/markdown-loader/-/markdown-loader-1.8.3.tgz", + "integrity": "sha512-QO3uUszxX63T+iXe7dB/rSDTpcZXrstyBIuI5NtWut/34Qsr/LZvMhrawwLBCtLjkQU1FJ/mlFXkXN8gebHx0g==", "dependencies": { - "@vuepress/markdown": "1.8.2", + "@vuepress/markdown": "1.8.3", "loader-utils": "^1.1.0", "lru-cache": "^5.1.1" } @@ -1883,9 +2228,9 @@ } }, "node_modules/@vuepress/plugin-active-header-links": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/plugin-active-header-links/-/plugin-active-header-links-1.8.2.tgz", - "integrity": "sha512-JmXAQg8D7J8mcKe2Ue3BZ9dOCzJMJXP4Cnkkc/IrqfDg0ET0l96gYWZohCqlvRIWt4f0VPiFAO4FLYrW+hko+g==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-active-header-links/-/plugin-active-header-links-1.8.3.tgz", + "integrity": "sha512-KNgo0IHUvzoJDAn0xw1HAIUnmnCYhoFCWS9fkgKhfJAKcdaSEZk+BHQKfZrKx1BINktTFNdBBGZmxANQdHlUFQ==", "dependencies": { "lodash.debounce": "^4.0.8" } @@ -1896,38 +2241,38 @@ "integrity": "sha512-27fQzRMsqGYpMf+ruyhsdfLv/n6z6b6LutFLE/pH66Itlh6ox9ew31x0pqYBbWIC/a4lBfXYUwFvi+DEvlb1EQ==" }, "node_modules/@vuepress/plugin-last-updated": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/plugin-last-updated/-/plugin-last-updated-1.8.2.tgz", - "integrity": "sha512-pYIRZi52huO9b6HY3JQNPKNERCLzMHejjBRt9ekdnJ1xhLs4MmRvt37BoXjI/qzvXkYtr7nmGgnKThNBVRTZuA==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-last-updated/-/plugin-last-updated-1.8.3.tgz", + "integrity": "sha512-anqktVt3RYyPVC+GxA0gBLWJZKQTXUcH5M+fFiO+tQvSizuhkzY/0i70Ss6znxv8ov5xwVn/C/gBGt21/9Qzvw==", "dependencies": { "cross-spawn": "^6.0.5" } }, "node_modules/@vuepress/plugin-nprogress": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/plugin-nprogress/-/plugin-nprogress-1.8.2.tgz", - "integrity": "sha512-3TOBee2NM3WLr1tdjDTGfrAMggjN+OlEPyKyv8FqThsVkDYhw48O3HwqlThp9KX7UbL3ExxIFBwWRFLC+kYrdw==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-nprogress/-/plugin-nprogress-1.8.3.tgz", + "integrity": "sha512-BSMDcON1sIib/JoQh+W4t7WGQeMIFKakZ931IGSeVhLwCmEUd4sgC1dSPFYWXGSxCoattcrFLwqP9gJ9vabJpw==", "dependencies": { "nprogress": "^0.2.0" } }, "node_modules/@vuepress/plugin-register-components": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/plugin-register-components/-/plugin-register-components-1.8.2.tgz", - "integrity": "sha512-6SUq3nHFMEh9qKFnjA8QnrNxj0kLs7+Gspq1OBU8vtu0NQmSvLFZVaMV7pzT/9zN2nO5Pld5qhsUJv1g71MrEA==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-register-components/-/plugin-register-components-1.8.3.tgz", + "integrity": "sha512-wp0nI4Eoysb8aHYonyezGV+Jlc2D6B2iEQ7bhSh+UBlBa5K2u9Q1i39He+W4bFwCO7KorfAqvHjRIzHnW095XA==", "dependencies": { - "@vuepress/shared-utils": "1.8.2" + "@vuepress/shared-utils": "1.8.3" } }, "node_modules/@vuepress/plugin-search": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/plugin-search/-/plugin-search-1.8.2.tgz", - "integrity": "sha512-JrSJr9o0Kar14lVtZ4wfw39pplxvvMh8vDBD9oW09a+6Zi/4bySPGdcdaqdqGW+OHSiZNvG+6uyfKSBBBqF6PA==" + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-search/-/plugin-search-1.8.3.tgz", + "integrity": "sha512-2ogShgbB5DaqUz6oYjjHHtczNTbOTSkh/9ve1bfYc6f0NasKauQeWUOCOE4lN9RAaP4hapTZ96g2G42VRlVmTg==" }, "node_modules/@vuepress/shared-utils": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/shared-utils/-/shared-utils-1.8.2.tgz", - "integrity": "sha512-6kGubc7iBDWruEBUU7yR+sQ++SOhMuvKWvWeTZJKRZedthycdzYz7QVpua0FaZSAJm5/dIt8ymU4WQvxTtZgTQ==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@vuepress/shared-utils/-/shared-utils-1.8.3.tgz", + "integrity": "sha512-OQ3MY920UZrQ8/NXlOf7kovNiXUJ5OHDKBuP+mqTrADNmuYeHNM667d2EyRniG5UFsAgdTCpGaidu1GlaCX8kw==", "dependencies": { "chalk": "^2.3.2", "escape-html": "^1.0.3", @@ -1941,13 +2286,13 @@ } }, "node_modules/@vuepress/theme-default": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/theme-default/-/theme-default-1.8.2.tgz", - "integrity": "sha512-rE7M1rs3n2xp4a/GrweO8EGwqFn3EA5gnFWdVmVIHyr7C1nix+EqjpPQF1SVWNnIrDdQuCw38PqS+oND1K2vYw==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@vuepress/theme-default/-/theme-default-1.8.3.tgz", + "integrity": "sha512-thMZ2+og9HWq4+F0xaHfGJvrtakIsmF51br1oOrlrZTb4aJIvBP2v3B26+r/sF9gx0BpPYyuQZ4H3xUthMCeNA==", "dependencies": { - "@vuepress/plugin-active-header-links": "1.8.2", - "@vuepress/plugin-nprogress": "1.8.2", - "@vuepress/plugin-search": "1.8.2", + "@vuepress/plugin-active-header-links": "1.8.3", + "@vuepress/plugin-nprogress": "1.8.3", + "@vuepress/plugin-search": "1.8.3", "docsearch.js": "^2.5.2", "lodash": "^4.17.15", "stylus": "^0.54.8", @@ -2382,9 +2727,9 @@ "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, "node_modules/asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", "dependencies": { "safer-buffer": "~2.1.0" } @@ -2491,15 +2836,15 @@ } }, "node_modules/autoprefixer": { - "version": "9.8.6", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz", - "integrity": "sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg==", + "version": "9.8.8", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.8.tgz", + "integrity": "sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA==", "dependencies": { "browserslist": "^4.12.0", "caniuse-lite": "^1.0.30001109", - "colorette": "^1.2.1", "normalize-range": "^0.1.2", "num2fraction": "^1.2.2", + "picocolors": "^0.2.1", "postcss": "^7.0.32", "postcss-value-parser": "^4.1.0" }, @@ -2533,9 +2878,9 @@ } }, "node_modules/babel-loader": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.2.tgz", - "integrity": "sha512-JvTd0/D889PQBtUXJ2PXaKU/pjZDMtHA9V2ecm+eNRmmBCMR09a+fmpGTNwnJtFmFl5Ei7Vy47LjBb+L0wQ99g==", + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz", + "integrity": "sha512-n4Zeta8NC3QAsuyiizu0GkmRcQ6clkV9WFUnUf1iXP//IeSKbWjofW3UHyZVwlOB4y039YQKefawyTn64Zwbuw==", "dependencies": { "find-cache-dir": "^3.3.1", "loader-utils": "^1.4.0", @@ -2559,12 +2904,12 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.0.tgz", - "integrity": "sha512-9bNwiR0dS881c5SHnzCmmGlMkJLl0OUZvxrxHo9w/iNoRuqaPjqlvBf4HrovXtQs/au5yKkpcdgfT1cC5PAZwg==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.0.tgz", + "integrity": "sha512-wMDoBJ6uG4u4PNFh72Ty6t3EgfA91puCuAwKIazbQlci+ENb/UU9A3xG5lutjUIiXCIn1CY5L15r9LimiJyrSA==", "dependencies": { "@babel/compat-data": "^7.13.11", - "@babel/helper-define-polyfill-provider": "^0.2.0", + "@babel/helper-define-polyfill-provider": "^0.3.0", "semver": "^6.1.1" }, "peerDependencies": { @@ -2572,23 +2917,23 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.0.tgz", - "integrity": "sha512-zZyi7p3BCUyzNxLx8KV61zTINkkV65zVkDAFNZmrTCRVhjo1jAS+YLvDJ9Jgd/w2tsAviCwFHReYfxO3Iql8Yg==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.4.0.tgz", + "integrity": "sha512-YxFreYwUfglYKdLUGvIF2nJEsGwj+RhWSX/ije3D2vQPOXuyMLMtg/cCGMDpOA7Nd+MwlNdnGODbd2EwUZPlsw==", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.2.0", - "core-js-compat": "^3.9.1" + "@babel/helper-define-polyfill-provider": "^0.3.0", + "core-js-compat": "^3.18.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.0.tgz", - "integrity": "sha512-J7vKbCuD2Xi/eEHxquHN14bXAW9CXtecwuLrOIDJtcZzTaPzV1VdEfoUf9AzcRBMolKUQKM9/GVojeh0hFiqMg==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.0.tgz", + "integrity": "sha512-dhAPTDLGoMW5/84wkgwiLRwMnio2i1fUe53EuvtKMv0pn2p3S8OCoV1xAzfJPl0KOX7IB89s2ib85vbYiea3jg==", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.2.0" + "@babel/helper-define-polyfill-provider": "^0.3.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" @@ -2706,39 +3051,42 @@ "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==" }, "node_modules/body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz", + "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==", "dependencies": { - "bytes": "3.1.0", + "bytes": "3.1.1", "content-type": "~1.0.4", "debug": "2.6.9", "depd": "~1.1.2", - "http-errors": "1.7.2", + "http-errors": "1.8.1", "iconv-lite": "0.4.24", "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" + "qs": "6.9.6", + "raw-body": "2.4.2", + "type-is": "~1.6.18" }, "engines": { "node": ">= 0.8" } }, "node_modules/body-parser/node_modules/bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", + "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==", "engines": { "node": ">= 0.8" } }, "node_modules/body-parser/node_modules/qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", + "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==", "engines": { "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/bonjour": { @@ -2983,15 +3331,15 @@ } }, "node_modules/browserslist": { - "version": "4.16.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.5.tgz", - "integrity": "sha512-C2HAjrM1AI/djrpAUU/tr4pml1DqLIzJKSLDBXBrNErl9ZCCTXdhwxdJjYc16953+mBWf7Lw+uUJgpgb8cN71A==", + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", + "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", "dependencies": { - "caniuse-lite": "^1.0.30001214", - "colorette": "^1.2.2", - "electron-to-chromium": "^1.3.719", + "caniuse-lite": "^1.0.30001286", + "electron-to-chromium": "^1.4.17", "escalade": "^3.1.1", - "node-releases": "^1.1.71" + "node-releases": "^2.0.1", + "picocolors": "^1.0.0" }, "bin": { "browserslist": "cli.js" @@ -3004,6 +3352,11 @@ "url": "https://opencollective.com/browserslist" } }, + "node_modules/browserslist/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/buffer": { "version": "4.9.2", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", @@ -3015,9 +3368,9 @@ } }, "node_modules/buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + "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/buffer-indexof": { "version": "1.1.1", @@ -3340,9 +3693,9 @@ } }, "node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.1.tgz", + "integrity": "sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA==", "engines": { "node": ">=10" }, @@ -3362,9 +3715,13 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001219", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001219.tgz", - "integrity": "sha512-c0yixVG4v9KBc/tQ2rlbB3A/bgBFRvl8h8M4IeUbqCca4gsiCfvtaheUssbnux/Mb66Vjz7x8yYjDgYcNQOhyQ==" + "version": "1.0.30001292", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001292.tgz", + "integrity": "sha512-jnT4Tq0Q4ma+6nncYQVe7d73kmDmE9C3OGTx3MvW7lBM/eY1S1DZTMBON7dqV481RhNiS5OxD7k9JQvmDOTirw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } }, "node_modules/caseless": { "version": "0.12.0", @@ -3445,6 +3802,30 @@ "fsevents": "^1.2.7" } }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/chokidar/node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chokidar2": { + "resolved": "node_modules/webpack/node_modules/watchpack/chokidar2", + "link": true + }, "node_modules/chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", @@ -3563,9 +3944,9 @@ } }, "node_modules/clean-css": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", - "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", + "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", "dependencies": { "source-map": "~0.6.0" }, @@ -3700,12 +4081,12 @@ } }, "node_modules/color": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/color/-/color-3.1.3.tgz", - "integrity": "sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", "dependencies": { - "color-convert": "^1.9.1", - "color-string": "^1.5.4" + "color-convert": "^1.9.3", + "color-string": "^1.6.0" } }, "node_modules/color-convert": { @@ -3722,19 +4103,14 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "node_modules/color-string": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.5.tgz", - "integrity": "sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.0.tgz", + "integrity": "sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ==", "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, - "node_modules/colorette": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", - "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==" - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -3868,16 +4244,35 @@ "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=" }, "node_modules/content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "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.1.2" + "safe-buffer": "5.2.1" }, "engines": { "node": ">= 0.6" } }, + "node_modules/content-disposition/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/content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", @@ -3887,17 +4282,17 @@ } }, "node_modules/convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", "dependencies": { "safe-buffer": "~5.1.1" } }, "node_modules/cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", "engines": { "node": ">= 0.6" } @@ -3992,6 +4387,26 @@ "node": ">=6" } }, + "node_modules/copy-webpack-plugin/node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/copy-webpack-plugin/node_modules/globby": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", @@ -4105,9 +4520,9 @@ } }, "node_modules/core-js": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.11.1.tgz", - "integrity": "sha512-k93Isqg7e4txZWMGNYwevZL9MiogLk8pd1PtwrmFmi8IBq4GXqUaVW/a33Llt6amSI36uSjd0GWwc9pTT9ALlQ==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.20.0.tgz", + "integrity": "sha512-KjbKU7UEfg4YPpskMtMXPhUKn7m/1OdTHTVjy09ScR2LVaoUXe8Jh0UdvN2EKUR6iKTJph52SJP95mAB0MnVLQ==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -4115,11 +4530,11 @@ } }, "node_modules/core-js-compat": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.11.1.tgz", - "integrity": "sha512-aZ0e4tmlG/aOBHj92/TuOuZwp6jFvn1WNabU5VOVixzhu5t5Ao+JZkQOPlgNXu6ynwLrwJxklT4Gw1G1VGEh+g==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.20.0.tgz", + "integrity": "sha512-relrah5h+sslXssTTOkvqcC/6RURifB0W5yhYBdBkaPYa5/2KBMiog3XiD+s3TwEHWxInWVv4Jx2/Lw0vng+IQ==", "dependencies": { - "browserslist": "^4.16.5", + "browserslist": "^4.19.1", "semver": "7.0.0" }, "funding": { @@ -4136,9 +4551,9 @@ } }, "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "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": "5.2.1", @@ -4728,9 +5143,9 @@ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, "node_modules/detect-node": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.5.tgz", - "integrity": "sha512-qi86tE6hRcFHy8jI1m2VG+LaPUR1LhqDa5G8tVjuUXmOrpuAgqsA1pN0+ldgr3aKUH+QLI9hCY/OcRYisERejw==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" }, "node_modules/diffie-hellman": { "version": "5.0.3", @@ -4960,9 +5375,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "node_modules/electron-to-chromium": { - "version": "1.3.723", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.723.tgz", - "integrity": "sha512-L+WXyXI7c7+G1V8ANzRsPI5giiimLAUDC6Zs1ojHHPhYXb3k/iTABFmWjivEtsWrRQymjnO66/rO2ZTABGdmWg==" + "version": "1.4.26", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.26.tgz", + "integrity": "sha512-cA1YwlRzO6TGp7yd3+KAqh9Tt6Z4CuuKqsAJP6uF/H5MQryjAGDhMhnY5cEXo8MaRCczpzSBhMPdqRIodkbZYw==" }, "node_modules/elliptic": { "version": "6.5.4", @@ -5088,26 +5503,30 @@ } }, "node_modules/es-abstract": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz", - "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", + "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", "dependencies": { "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", "has": "^1.0.3", "has-symbols": "^1.0.2", - "is-callable": "^1.2.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.2", - "is-string": "^1.0.5", - "object-inspect": "^1.9.0", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.1", + "is-string": "^1.0.7", + "is-weakref": "^1.0.1", + "object-inspect": "^1.11.0", "object-keys": "^1.1.1", "object.assign": "^4.1.2", "string.prototype.trimend": "^1.0.4", "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.0" + "unbox-primitive": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -5210,9 +5629,9 @@ } }, "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "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" } @@ -5385,16 +5804,16 @@ } }, "node_modules/express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz", + "integrity": "sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==", "dependencies": { "accepts": "~1.3.7", "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", + "body-parser": "1.19.1", + "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.0", + "cookie": "0.4.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "~1.1.2", @@ -5408,13 +5827,13 @@ "on-finished": "~2.3.0", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", + "proxy-addr": "~2.0.7", + "qs": "6.9.6", "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", + "safe-buffer": "5.2.1", + "send": "0.17.2", + "serve-static": "1.14.2", + "setprototypeof": "1.2.0", "statuses": "~1.5.0", "type-is": "~1.6.18", "utils-merge": "1.0.1", @@ -5430,13 +5849,35 @@ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, "node_modules/express/node_modules/qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", + "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==", "engines": { "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/express/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/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -5511,15 +5952,35 @@ "node": ">=4.0.0" } }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.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/faye-websocket": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", - "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "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" }, @@ -5612,9 +6073,9 @@ } }, "node_modules/find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", "dependencies": { "commondir": "^1.0.1", "make-dir": "^3.0.2", @@ -5702,9 +6163,9 @@ } }, "node_modules/forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "engines": { "node": ">= 0.6" } @@ -5837,6 +6298,21 @@ "node": ">=6" } }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", @@ -5873,23 +6349,14 @@ } }, "node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "node_modules/glob-parent/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dependencies": { - "is-extglob": "^2.1.0" + "is-glob": "^4.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 6" } }, "node_modules/glob-to-regexp": { @@ -6069,6 +6536,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -6309,29 +6790,24 @@ "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=" }, "node_modules/http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "dependencies": { "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "toidentifier": "1.0.1" }, "engines": { "node": ">= 0.6" } }, - "node_modules/http-errors/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, "node_modules/http-parser-js": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", - "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==" + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.5.tgz", + "integrity": "sha512-x+JVEkO2PoM8qqpbPbOL3cqHPwerep7OwzK7Ay+sMQjKzaKCqWvjoXm5tqMP9tXWWTnTzAjIhXg+J99XYuPhPA==" }, "node_modules/http-proxy": { "version": "1.18.1", @@ -6602,6 +7078,19 @@ "node": ">=6" } }, + "node_modules/internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dependencies": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", @@ -6643,11 +7132,12 @@ } }, "node_modules/is-arguments": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz", - "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", "dependencies": { - "call-bind": "^1.0.0" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -6662,9 +7152,12 @@ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "node_modules/is-bigint": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.1.tgz", - "integrity": "sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dependencies": { + "has-bigints": "^1.0.1" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6681,11 +7174,12 @@ } }, "node_modules/is-boolean-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz", - "integrity": "sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", "dependencies": { - "call-bind": "^1.0.0" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -6700,9 +7194,9 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "node_modules/is-callable": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", - "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", "engines": { "node": ">= 0.4" }, @@ -6757,9 +7251,12 @@ } }, "node_modules/is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -6822,9 +7319,9 @@ } }, "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "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" }, @@ -6848,9 +7345,9 @@ } }, "node_modules/is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", "engines": { "node": ">= 0.4" }, @@ -6878,9 +7375,12 @@ } }, "node_modules/is-number-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", - "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", + "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -6970,12 +7470,12 @@ "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" }, "node_modules/is-regex": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", - "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "dependencies": { "call-bind": "^1.0.2", - "has-symbols": "^1.0.1" + "has-tostringtag": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -6989,6 +7489,14 @@ "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==" }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", + "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -6998,9 +7506,12 @@ } }, "node_modules/is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -7009,11 +7520,11 @@ } }, "node_modules/is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", "dependencies": { - "has-symbols": "^1.0.1" + "has-symbols": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -7027,6 +7538,17 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -7353,9 +7875,9 @@ "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" }, "node_modules/loglevel": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz", - "integrity": "sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz", + "integrity": "sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA==", "engines": { "node": ">= 0.6.0" }, @@ -7641,9 +8163,9 @@ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" }, "node_modules/mime": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", - "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "bin": { "mime": "cli.js" }, @@ -7652,19 +8174,19 @@ } }, "node_modules/mime-db": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", - "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==", + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.30", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", - "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", + "version": "2.1.34", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", + "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", "dependencies": { - "mime-db": "1.47.0" + "mime-db": "1.51.0" }, "engines": { "node": ">= 0.6" @@ -7843,9 +8365,9 @@ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=" }, "node_modules/nan": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", - "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", "optional": true }, "node_modules/nanomatch": { @@ -7962,9 +8484,9 @@ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, "node_modules/node-releases": { - "version": "1.1.71", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz", - "integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", + "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==" }, "node_modules/nopt": { "version": "1.0.10", @@ -8136,9 +8658,9 @@ } }, "node_modules/object-inspect": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.2.tgz", - "integrity": "sha512-gz58rdPpadwztRrPjZE9DZLOABUpTGdcANUgOwBFO1C+HZZhePoP83M65WGDmbpwFYJSWqavbl4SgDn4k8RYTA==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -8195,13 +8717,13 @@ } }, "node_modules/object.getownpropertydescriptors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz", - "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.3.tgz", + "integrity": "sha512-VdDoCwvJI4QdC6ndjpqFmoL3/+HxffFBbcJzKi5hwLLqqx3mdbedRpfZDdK0SrOSauj8X4GzBvnDZl4vTN7dOw==", "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2" + "es-abstract": "^1.19.1" }, "engines": { "node": ">= 0.8" @@ -8222,14 +8744,13 @@ } }, "node_modules/object.values": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.3.tgz", - "integrity": "sha512-nkF6PfDB9alkOUxpf1HNm/QlkeW3SReqL5WXeBLpEJJnlPSvRaDQpW3gQTksTN3fgJX4hL42RzKyOin6ff3tyw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2", - "has": "^1.0.3" + "es-abstract": "^1.19.1" }, "engines": { "node": ">= 0.4" @@ -9220,9 +9741,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.5.tgz", - "integrity": "sha512-aFYPoYmXbZ1V6HZaSvat08M97A8HqO6Pjz+PiNpw/DhuRrC72XWAdp3hL6wusDCN31sSmcZyMGa2hZEuX+Xfhg==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.8.tgz", + "integrity": "sha512-D5PG53d209Z1Uhcc0qAZ5U3t5HagH3cxu+WLZ22jt3gLUpXM4eXXfiO14jiDWST3NNooX/E8wISfOhZ9eIjGTQ==", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -9263,9 +9784,9 @@ } }, "node_modules/postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" + "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/node_modules/source-map": { "version": "0.6.1", @@ -9284,15 +9805,15 @@ } }, "node_modules/prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", "optional": true, "bin": { "prettier": "bin-prettier.js" }, "engines": { - "node": ">=4" + "node": ">=10.13.0" } }, "node_modules/pretty-error": { @@ -9344,11 +9865,11 @@ "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" }, "node_modules/proxy-addr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", - "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "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.1.2", + "forwarded": "0.2.0", "ipaddr.js": "1.9.1" }, "engines": { @@ -9635,12 +10156,12 @@ } }, "node_modules/raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", + "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==", "dependencies": { - "bytes": "3.1.0", - "http-errors": "1.7.2", + "bytes": "3.1.1", + "http-errors": "1.8.1", "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, @@ -9649,9 +10170,9 @@ } }, "node_modules/raw-body/node_modules/bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", + "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==", "engines": { "node": ">= 0.8" } @@ -9711,20 +10232,20 @@ "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" }, "node_modules/regenerate-unicode-properties": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", - "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz", + "integrity": "sha512-3E12UeNSPfjrgwjkR81m5J7Aw/T55Tu7nUyZVQYCKEOs+2dkxEY+DpPtZzO4YruuiPb7NkYLVcyJC4+zCbk5pA==", "dependencies": { - "regenerate": "^1.4.0" + "regenerate": "^1.4.2" }, "engines": { "node": ">=4" } }, "node_modules/regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" }, "node_modules/regenerator-transform": { "version": "0.14.5", @@ -9785,16 +10306,16 @@ } }, "node_modules/regexpu-core": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", - "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.8.0.tgz", + "integrity": "sha512-1F6bYsoYiz6is+oz70NWur2Vlh9KWtswuRuzJOfeYUrfPX2o8n74AnUVaOGDbUqVGO9fNHu48/pjJO4sNVwsOg==", "dependencies": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.2.0", - "regjsgen": "^0.5.1", - "regjsparser": "^0.6.4", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.2.0" + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^9.0.0", + "regjsgen": "^0.5.2", + "regjsparser": "^0.7.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.0.0" }, "engines": { "node": ">=4" @@ -9828,9 +10349,9 @@ "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==" }, "node_modules/regjsparser": { - "version": "0.6.9", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.9.tgz", - "integrity": "sha512-ZqbNRz1SNjLAiYuwY0zoXW8Ne675IX5q+YHioAGbCw4X96Mjl2+dcX9B2ciaeyYjViDAfvIjFpQjJgLttTEERQ==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.7.0.tgz", + "integrity": "sha512-A4pcaORqmNMDVwUjWoTzuhwMGpP+NykpfqAsEgI1FSH/EzC7lrN5TMd+kN8YCovX+jMpu8eaqXgXPCa0g8FQNQ==", "dependencies": { "jsesc": "~0.5.0" }, @@ -10112,9 +10633,9 @@ "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=" }, "node_modules/selfsigned": { - "version": "1.10.8", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz", - "integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==", + "version": "1.10.11", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.11.tgz", + "integrity": "sha512-aVmbPOfViZqOZPgRBT0+3u4yZFHpmnIghLMlAcb5/xhp5ZtB/RVnKhz5vl2M32CLXAqR4kha9zfhNg0Lf/sxKA==", "dependencies": { "node-forge": "^0.10.0" } @@ -10139,9 +10660,9 @@ } }, "node_modules/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", + "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", "dependencies": { "debug": "2.6.9", "depd": "~1.1.2", @@ -10150,9 +10671,9 @@ "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "~1.7.2", + "http-errors": "1.8.1", "mime": "1.6.0", - "ms": "2.1.1", + "ms": "2.1.3", "on-finished": "~2.3.0", "range-parser": "~1.2.1", "statuses": "~1.5.0" @@ -10173,9 +10694,9 @@ } }, "node_modules/send/node_modules/ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/serialize-javascript": { "version": "4.0.0", @@ -10227,14 +10748,14 @@ "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" }, "node_modules/serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", + "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.1" + "send": "0.17.2" }, "engines": { "node": ">= 0.8.0" @@ -10265,9 +10786,9 @@ "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" }, "node_modules/setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "node_modules/sha.js": { "version": "2.4.11", @@ -10300,6 +10821,19 @@ "node": ">=0.10.0" } }, + "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.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", @@ -10495,26 +11029,26 @@ } }, "node_modules/sockjs": { - "version": "0.3.21", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.21.tgz", - "integrity": "sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw==", + "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": "^3.4.0", + "uuid": "^8.3.2", "websocket-driver": "^0.7.4" } }, "node_modules/sockjs-client": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.5.1.tgz", - "integrity": "sha512-VnVAb663fosipI/m6pqRXakEOw7nvd7TUgdr3PlR/8V2I95QIdwT8L4nMxhyU8SmDBHYXU1TOElaKOmKLfYzeQ==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.5.2.tgz", + "integrity": "sha512-ZzRxPBISQE7RpzlH4tKJMQbHM9pabHluk0WBaxAQ+wm/UieeBVBou0p4wVnSQGN9QmpAZygQ0cDIypWuqOFmFQ==", "dependencies": { "debug": "^3.2.6", "eventsource": "^1.0.7", "faye-websocket": "^0.11.3", "inherits": "^2.0.4", "json3": "^3.3.3", - "url-parse": "^1.5.1" + "url-parse": "^1.5.3" } }, "node_modules/sockjs-client/node_modules/debug": { @@ -10530,6 +11064,14 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/sockjs/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/sort-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", @@ -10567,9 +11109,9 @@ } }, "node_modules/source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "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" @@ -10617,9 +11159,9 @@ } }, "node_modules/spdy-transport/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dependencies": { "ms": "2.1.2" }, @@ -10651,9 +11193,9 @@ } }, "node_modules/spdy/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dependencies": { "ms": "2.1.2" }, @@ -10863,17 +11405,17 @@ } }, "node_modules/std-env": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-2.3.0.tgz", - "integrity": "sha512-4qT5B45+Kjef2Z6pE0BkskzsH0GO7GrND0wGlTM1ioUe3v0dGYx9ZJH0Aro/YyA8fqQ5EyIKDRjZojJYMFTflw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-2.3.1.tgz", + "integrity": "sha512-eOsoKTWnr6C8aWrqJJ2KAReXoa7Vn5Ywyw6uCXgA/xDhxPoaIsBa5aNJmISY04dLwXPBnDHW4diGM7Sn5K4R/g==", "dependencies": { - "ci-info": "^3.0.0" + "ci-info": "^3.1.1" } }, "node_modules/std-env/node_modules/ci-info": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.1.1.tgz", - "integrity": "sha512-kdRWLBIJwdsYJWYJFtAFFYxybguqeF91qpZaggjG5Nf8QKdizFG2hjqvaTXbxFIcYbSaD74KpAXv6BSm17DHEQ==" + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz", + "integrity": "sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==" }, "node_modules/stream-browserify": { "version": "2.0.2", @@ -11086,6 +11628,7 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", + "deprecated": "This SVGO version is no longer supported. Upgrade to v2.x.x.", "dependencies": { "chalk": "^2.4.1", "coa": "^2.0.2", @@ -11487,9 +12030,9 @@ } }, "node_modules/toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "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" } @@ -11631,37 +12174,37 @@ } }, "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", - "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "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-match-property-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", - "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "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": "^1.0.4", - "unicode-property-aliases-ecmascript": "^1.0.4" + "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": "1.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", - "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", + "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", "engines": { "node": ">=4" } }, "node_modules/unicode-property-aliases-ecmascript": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", - "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", + "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", "engines": { "node": ">=4" } @@ -11964,6 +12507,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", "engines": { "node": ">=0.4.x" } @@ -12025,6 +12569,7 @@ "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", "bin": { "uuid": "bin/uuid" } @@ -12064,6 +12609,11 @@ "extsprintf": "^1.2.0" } }, + "node_modules/verror/node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, "node_modules/vm-browserify": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", @@ -12078,9 +12628,9 @@ } }, "node_modules/vue": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.12.tgz", - "integrity": "sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg==" + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz", + "integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==" }, "node_modules/vue-hot-reload-api": { "version": "2.3.4", @@ -12088,9 +12638,9 @@ "integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==" }, "node_modules/vue-loader": { - "version": "15.9.6", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.9.6.tgz", - "integrity": "sha512-j0cqiLzwbeImIC6nVIby2o/ABAWhlppyL/m5oJ67R5MloP0hj/DtFgb0Zmq3J9CG7AJ+AXIvHVnJAPBvrLyuDg==", + "version": "15.9.8", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.9.8.tgz", + "integrity": "sha512-GwSkxPrihfLR69/dSV3+5CdMQ0D+jXg8Ma1S4nQXKJAznYFX14vHdc/NetQc34Dw+rBbIJyP7JOuVb9Fhprvog==", "dependencies": { "@vue/component-compiler-utils": "^3.1.0", "hash-sum": "^1.0.2", @@ -12112,14 +12662,14 @@ } }, "node_modules/vue-router": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.5.1.tgz", - "integrity": "sha512-RRQNLT8Mzr8z7eL4p7BtKvRaTSGdCbTy2+Mm5HTJvLGYSSeG9gDzNasJPP/yOYKLy+/cLG/ftrqq5fvkFwBJEw==" + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.5.3.tgz", + "integrity": "sha512-FUlILrW3DGitS2h+Xaw8aRNvGTwtuaxrRkNSHWTizOfLUie7wuYwezeZ50iflRn8YPV5kxmU2LQuu3nM/b3Zsg==" }, "node_modules/vue-server-renderer": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/vue-server-renderer/-/vue-server-renderer-2.6.12.tgz", - "integrity": "sha512-3LODaOsnQx7iMFTBLjki8xSyOxhCtbZ+nQie0wWY4iOVeEtTg1a3YQAjd82WvKxrWHHTshjvLb7OXMc2/dYuxw==", + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vue-server-renderer/-/vue-server-renderer-2.6.14.tgz", + "integrity": "sha512-HifYRa/LW7cKywg9gd4ZtvtRuBlstQBao5ZCWlg40fyB4OPoGfEXAzxb0emSLv4pBDOHYx0UjpqvxpiQFEuoLA==", "dependencies": { "chalk": "^1.1.3", "hash-sum": "^1.0.2", @@ -12207,9 +12757,9 @@ } }, "node_modules/vue-template-compiler": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.12.tgz", - "integrity": "sha512-OzzZ52zS41YUbkCBfdXShQTe69j1gQDZ9HIX8miuC9C3rBCk9wIRjLiZZLrmX9V+Ftq/YEyv1JaVr5Y/hNtByg==", + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz", + "integrity": "sha512-ODQS1SyMbjKoO1JBJZojSw6FE4qnh9rIpUZn2EUT86FKizx9uH5z6uXiIrm4/Nb/gwxTi/o17ZDEGWAXHvtC7g==", "dependencies": { "de-indent": "^1.0.2", "he": "^1.1.0" @@ -12221,13 +12771,13 @@ "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==" }, "node_modules/vuepress": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/vuepress/-/vuepress-1.8.2.tgz", - "integrity": "sha512-BU1lUDwsA3ghf7a9ga4dsf0iTc++Z/l7BR1kUagHWVBHw7HNRgRDfAZBDDQXhllMILVToIxaTifpne9mSi94OA==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/vuepress/-/vuepress-1.8.3.tgz", + "integrity": "sha512-1iro+COj3lAXiIS0B3HApYzFZxE6GHmXjxoYrZJvFds3W3gcynxw/umM5hxA+goyx8n/OtsuHN60/qstz9gDVQ==", "hasInstallScript": true, "dependencies": { - "@vuepress/core": "1.8.2", - "@vuepress/theme-default": "1.8.2", + "@vuepress/core": "1.8.3", + "@vuepress/theme-default": "1.8.3", "cac": "^6.5.6", "envinfo": "^7.2.0", "opencollective-postinstall": "^2.0.2", @@ -12305,11 +12855,10 @@ } }, "node_modules/vuepress-plugin-container": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/vuepress-plugin-container/-/vuepress-plugin-container-2.1.5.tgz", - "integrity": "sha512-TQrDX/v+WHOihj3jpilVnjXu9RcTm6m8tzljNJwYhxnJUW0WWQ0hFLcDTqTBwgKIFdEiSxVOmYE+bJX/sq46MA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/vuepress-plugin-container/-/vuepress-plugin-container-2.1.4.tgz", + "integrity": "sha512-l+EkeL+rC6DJch1wAZUFIkNDaz2TNOg4NQTHa3yMAsYkC+QaSRubGdN6YwOSmfjxVmM9s9D3gwBWw0O7OBhqRg==", "dependencies": { - "@vuepress/shared-utils": "^1.2.0", "markdown-it-container": "^2.0.0" } }, @@ -12385,15 +12934,6 @@ "node": ">=10.13.0" } }, - "node_modules/watchpack-chokidar2": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", - "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", - "optional": true, - "dependencies": { - "chokidar": "^2.1.8" - } - }, "node_modules/watchpack/node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", @@ -12414,9 +12954,9 @@ "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" }, "node_modules/webpack": { - "version": "4.46.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz", - "integrity": "sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.43.0.tgz", + "integrity": "sha512-GW1LjnPipFW2Y78OOab8NJlCflB7EFskMih2AHdvjbpKMeDJqEgSx24cXXXiPS65+WSwVyxtDsJH6jGX2czy+g==", "dependencies": { "@webassemblyjs/ast": "1.9.0", "@webassemblyjs/helper-module-context": "1.9.0", @@ -12426,7 +12966,7 @@ "ajv": "^6.10.2", "ajv-keywords": "^3.4.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^4.5.0", + "enhanced-resolve": "^4.1.0", "eslint-scope": "^4.0.3", "json-parse-better-errors": "^1.0.2", "loader-runner": "^2.4.0", @@ -12439,7 +12979,7 @@ "schema-utils": "^1.0.0", "tapable": "^1.1.3", "terser-webpack-plugin": "^1.4.3", - "watchpack": "^1.7.4", + "watchpack": "^1.6.1", "webpack-sources": "^1.4.1" }, "bin": { @@ -12451,14 +12991,6 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - }, - "webpack-command": { - "optional": true - } } }, "node_modules/webpack-chain": { @@ -12565,9 +13097,9 @@ } }, "node_modules/webpack-dev-server/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dependencies": { "ms": "2.1.2" }, @@ -12756,18 +13288,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/webpack/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==", - "optional": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/webpack/node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -12838,16 +13358,26 @@ } }, "node_modules/webpack/node_modules/watchpack": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", - "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.1.tgz", + "integrity": "sha512-1OeW6LucExk7h6lBuCr1isK5261Tf0PHNRG9tZjg2WKUsSkPwvyv37d7mgAUk1rZjxxaL/6WttSGMUY2hn/20g==", "dependencies": { "graceful-fs": "^4.1.2", "neo-async": "^2.5.0" }, "optionalDependencies": { - "chokidar": "^3.4.1", - "watchpack-chokidar2": "^2.0.1" + "chokidar": "^3.4.0", + "chokidar2": "file:./chokidar2" + } + }, + "node_modules/webpack/node_modules/watchpack/chokidar2": { + "version": "2.0.0", + "optional": true, + "dependencies": { + "chokidar": "^2.1.8" + }, + "engines": { + "node": "<8.10.0" } }, "node_modules/webpackbar": { @@ -13331,32 +13861,32 @@ } }, "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", + "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", "requires": { - "@babel/highlight": "^7.12.13" + "@babel/highlight": "^7.16.0" } }, "@babel/compat-data": { - "version": "7.13.15", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.13.15.tgz", - "integrity": "sha512-ltnibHKR1VnrU4ymHyQ/CXtNXI6yZC0oJThyW78Hft8XndANwi+9H+UIklBDraIjFEJzw8wmcM427oDd9KS5wA==" + "version": "7.16.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.4.tgz", + "integrity": "sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q==" }, "@babel/core": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.13.16.tgz", - "integrity": "sha512-sXHpixBiWWFti0AV2Zq7avpTasr6sIAu7Y396c608541qAU2ui4a193m0KSQmfPSKFZLnQ3cvlKDOm3XkuXm3Q==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.13.16", - "@babel/helper-compilation-targets": "^7.13.16", - "@babel/helper-module-transforms": "^7.13.14", - "@babel/helpers": "^7.13.16", - "@babel/parser": "^7.13.16", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.13.15", - "@babel/types": "^7.13.16", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.5.tgz", + "integrity": "sha512-wUcenlLzuWMZ9Zt8S0KmFwGlH6QKRh3vsm/dhDA3CHkiTA45YuG1XkHRcNRl73EFPXDp/d5kVOU0/y7x2w6OaQ==", + "requires": { + "@babel/code-frame": "^7.16.0", + "@babel/generator": "^7.16.5", + "@babel/helper-compilation-targets": "^7.16.3", + "@babel/helper-module-transforms": "^7.16.5", + "@babel/helpers": "^7.16.5", + "@babel/parser": "^7.16.5", + "@babel/template": "^7.16.0", + "@babel/traverse": "^7.16.5", + "@babel/types": "^7.16.0", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -13366,9 +13896,9 @@ }, "dependencies": { "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "requires": { "ms": "2.1.2" } @@ -13394,11 +13924,11 @@ } }, "@babel/generator": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.16.tgz", - "integrity": "sha512-grBBR75UnKOcUWMp8WoDxNsWCFl//XCK6HWTrBQKTr5SV9f5g0pNOjdyzi/DTBv12S9GnYPInIXQBTky7OXEMg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.5.tgz", + "integrity": "sha512-kIvCdjZqcdKqoDbVVdt5R99icaRtrtYhYK/xux5qiWCBmfdvEYMFZ68QCrpE5cbFM1JsuArUNs1ZkuKtTtUcZA==", "requires": { - "@babel/types": "^7.13.16", + "@babel/types": "^7.16.0", "jsesc": "^2.5.1", "source-map": "^0.5.0" }, @@ -13411,58 +13941,60 @@ } }, "@babel/helper-annotate-as-pure": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz", - "integrity": "sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.0.tgz", + "integrity": "sha512-ItmYF9vR4zA8cByDocY05o0LGUkp1zhbTQOH1NFyl5xXEqlTJQCEJjieriw+aFpxo16swMxUnUiKS7a/r4vtHg==", "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.16.0" } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.12.13.tgz", - "integrity": "sha512-CZOv9tGphhDRlVjVkAgm8Nhklm9RzSmWpX2my+t7Ua/KT616pEzXsQCjinzvkRvHWJ9itO4f296efroX23XCMA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.5.tgz", + "integrity": "sha512-3JEA9G5dmmnIWdzaT9d0NmFRgYnWUThLsDaL7982H0XqqWr56lRrsmwheXFMjR+TMl7QMBb6mzy9kvgr1lRLUA==", "requires": { - "@babel/helper-explode-assignable-expression": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/helper-explode-assignable-expression": "^7.16.0", + "@babel/types": "^7.16.0" } }, "@babel/helper-compilation-targets": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.16.tgz", - "integrity": "sha512-3gmkYIrpqsLlieFwjkGgLaSHmhnvlAYzZLlYVjlW+QwI+1zE17kGxuJGmIqDQdYp56XdmGeD+Bswx0UTyG18xA==", + "version": "7.16.3", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.3.tgz", + "integrity": "sha512-vKsoSQAyBmxS35JUOOt+07cLc6Nk/2ljLIHwmq2/NM6hdioUaqEXq/S+nXvbvXbZkNDlWOymPanJGOc4CBjSJA==", "requires": { - "@babel/compat-data": "^7.13.15", - "@babel/helper-validator-option": "^7.12.17", - "browserslist": "^4.14.5", + "@babel/compat-data": "^7.16.0", + "@babel/helper-validator-option": "^7.14.5", + "browserslist": "^4.17.5", "semver": "^6.3.0" } }, "@babel/helper-create-class-features-plugin": { - "version": "7.13.11", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.13.11.tgz", - "integrity": "sha512-ays0I7XYq9xbjCSvT+EvysLgfc3tOkwCULHjrnscGT3A9qD4sk3wXnJ3of0MAWsWGjdinFvajHU2smYuqXKMrw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.5.tgz", + "integrity": "sha512-NEohnYA7mkB8L5JhU7BLwcBdU3j83IziR9aseMueWGeAjblbul3zzb8UvJ3a1zuBiqCMObzCJHFqKIQE6hTVmg==", "requires": { - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-member-expression-to-functions": "^7.13.0", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/helper-replace-supers": "^7.13.0", - "@babel/helper-split-export-declaration": "^7.12.13" + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-environment-visitor": "^7.16.5", + "@babel/helper-function-name": "^7.16.0", + "@babel/helper-member-expression-to-functions": "^7.16.5", + "@babel/helper-optimise-call-expression": "^7.16.0", + "@babel/helper-replace-supers": "^7.16.5", + "@babel/helper-split-export-declaration": "^7.16.0" } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.12.17", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.17.tgz", - "integrity": "sha512-p2VGmBu9oefLZ2nQpgnEnG0ZlRPvL8gAGvPUMQwUdaE8k49rOMuZpOwdQoy5qJf6K8jL3bcAMhVUlHAjIgJHUg==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.16.0.tgz", + "integrity": "sha512-3DyG0zAFAZKcOp7aVr33ddwkxJ0Z0Jr5V99y3I690eYLpukJsJvAbzTy1ewoCqsML8SbIrjH14Jc/nSQ4TvNPA==", "requires": { - "@babel/helper-annotate-as-pure": "^7.12.13", + "@babel/helper-annotate-as-pure": "^7.16.0", "regexpu-core": "^4.7.1" } }, "@babel/helper-define-polyfill-provider": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.0.tgz", - "integrity": "sha512-JT8tHuFjKBo8NnaUbblz7mIu1nnvUDiHVjXXkulZULyidvo/7P6TY7+YqpV37IfF+KUFxmlK04elKtGKXaiVgw==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.0.tgz", + "integrity": "sha512-7hfT8lUljl/tM3h+izTX/pO3W3frz2ok6Pk+gzys8iJqDfZrZy2pXjRTZAvG2YmfHun1X4q8/UZRLatMfqc5Tg==", "requires": { "@babel/helper-compilation-targets": "^7.13.0", "@babel/helper-module-imports": "^7.12.13", @@ -13475,9 +14007,9 @@ }, "dependencies": { "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "requires": { "ms": "2.1.2" } @@ -13489,316 +14021,353 @@ } } }, + "@babel/helper-environment-visitor": { + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.5.tgz", + "integrity": "sha512-ODQyc5AnxmZWm/R2W7fzhamOk1ey8gSguo5SGvF0zcB3uUzRpTRmM/jmLSm9bDMyPlvbyJ+PwPEK0BWIoZ9wjg==", + "requires": { + "@babel/types": "^7.16.0" + } + }, "@babel/helper-explode-assignable-expression": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.13.0.tgz", - "integrity": "sha512-qS0peLTDP8kOisG1blKbaoBg/o9OSa1qoumMjTK5pM+KDTtpxpsiubnCGP34vK8BXGcb2M9eigwgvoJryrzwWA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.0.tgz", + "integrity": "sha512-Hk2SLxC9ZbcOhLpg/yMznzJ11W++lg5GMbxt1ev6TXUiJB0N42KPC+7w8a+eWGuqDnUYuwStJoZHM7RgmIOaGQ==", "requires": { - "@babel/types": "^7.13.0" + "@babel/types": "^7.16.0" } }, "@babel/helper-function-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", - "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz", + "integrity": "sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog==", "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/helper-get-function-arity": "^7.16.0", + "@babel/template": "^7.16.0", + "@babel/types": "^7.16.0" } }, "@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz", + "integrity": "sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ==", "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.16.0" } }, "@babel/helper-hoist-variables": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.13.16.tgz", - "integrity": "sha512-1eMtTrXtrwscjcAeO4BVK+vvkxaLJSPFz1w1KLawz6HLNi9bPFGBNwwDyVfiu1Tv/vRRFYfoGaKhmAQPGPn5Wg==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz", + "integrity": "sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg==", "requires": { - "@babel/traverse": "^7.13.15", - "@babel/types": "^7.13.16" + "@babel/types": "^7.16.0" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz", - "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.5.tgz", + "integrity": "sha512-7fecSXq7ZrLE+TWshbGT+HyCLkxloWNhTbU2QM1NTI/tDqyf0oZiMcEfYtDuUDCo528EOlt39G1rftea4bRZIw==", "requires": { - "@babel/types": "^7.13.12" + "@babel/types": "^7.16.0" } }, "@babel/helper-module-imports": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz", - "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz", + "integrity": "sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg==", "requires": { - "@babel/types": "^7.13.12" + "@babel/types": "^7.16.0" } }, "@babel/helper-module-transforms": { - "version": "7.13.14", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.13.14.tgz", - "integrity": "sha512-QuU/OJ0iAOSIatyVZmfqB0lbkVP0kDRiKj34xy+QNsnVZi/PA6BoSoreeqnxxa9EHFAIL0R9XOaAR/G9WlIy5g==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.5.tgz", + "integrity": "sha512-CkvMxgV4ZyyioElFwcuWnDCcNIeyqTkCm9BxXZi73RR1ozqlpboqsbGUNvRTflgZtFbbJ1v5Emvm+lkjMYY/LQ==", "requires": { - "@babel/helper-module-imports": "^7.13.12", - "@babel/helper-replace-supers": "^7.13.12", - "@babel/helper-simple-access": "^7.13.12", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/helper-validator-identifier": "^7.12.11", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.13.13", - "@babel/types": "^7.13.14" + "@babel/helper-environment-visitor": "^7.16.5", + "@babel/helper-module-imports": "^7.16.0", + "@babel/helper-simple-access": "^7.16.0", + "@babel/helper-split-export-declaration": "^7.16.0", + "@babel/helper-validator-identifier": "^7.15.7", + "@babel/template": "^7.16.0", + "@babel/traverse": "^7.16.5", + "@babel/types": "^7.16.0" } }, "@babel/helper-optimise-call-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", - "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.0.tgz", + "integrity": "sha512-SuI467Gi2V8fkofm2JPnZzB/SUuXoJA5zXe/xzyPP2M04686RzFKFHPK6HDVN6JvWBIEW8tt9hPR7fXdn2Lgpw==", "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.16.0" } }, "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==" + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.5.tgz", + "integrity": "sha512-59KHWHXxVA9K4HNF4sbHCf+eJeFe0Te/ZFGqBT4OjXhrwvA04sGfaEGsVTdsjoszq0YTP49RC9UKe5g8uN2RwQ==" }, "@babel/helper-remap-async-to-generator": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.13.0.tgz", - "integrity": "sha512-pUQpFBE9JvC9lrQbpX0TmeNIy5s7GnZjna2lhhcHC7DzgBs6fWn722Y5cfwgrtrqc7NAJwMvOa0mKhq6XaE4jg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.5.tgz", + "integrity": "sha512-X+aAJldyxrOmN9v3FKp+Hu1NO69VWgYgDGq6YDykwRPzxs5f2N+X988CBXS7EQahDU+Vpet5QYMqLk+nsp+Qxw==", "requires": { - "@babel/helper-annotate-as-pure": "^7.12.13", - "@babel/helper-wrap-function": "^7.13.0", - "@babel/types": "^7.13.0" + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-wrap-function": "^7.16.5", + "@babel/types": "^7.16.0" } }, "@babel/helper-replace-supers": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.13.12.tgz", - "integrity": "sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.5.tgz", + "integrity": "sha512-ao3seGVa/FZCMCCNDuBcqnBFSbdr8N2EW35mzojx3TwfIbdPmNK+JV6+2d5bR0Z71W5ocLnQp9en/cTF7pBJiQ==", "requires": { - "@babel/helper-member-expression-to-functions": "^7.13.12", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/traverse": "^7.13.0", - "@babel/types": "^7.13.12" + "@babel/helper-environment-visitor": "^7.16.5", + "@babel/helper-member-expression-to-functions": "^7.16.5", + "@babel/helper-optimise-call-expression": "^7.16.0", + "@babel/traverse": "^7.16.5", + "@babel/types": "^7.16.0" } }, "@babel/helper-simple-access": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz", - "integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz", + "integrity": "sha512-o1rjBT/gppAqKsYfUdfHq5Rk03lMQrkPHG1OWzHWpLgVXRH4HnMM9Et9CVdIqwkCQlobnGHEJMsgWP/jE1zUiw==", "requires": { - "@babel/types": "^7.13.12" + "@babel/types": "^7.16.0" } }, "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz", - "integrity": "sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", + "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==", "requires": { - "@babel/types": "^7.12.1" + "@babel/types": "^7.16.0" } }, "@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz", + "integrity": "sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw==", "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.16.0" } }, "@babel/helper-validator-identifier": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", - "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==" + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==" }, "@babel/helper-validator-option": { - "version": "7.12.17", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz", - "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==" + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", + "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==" }, "@babel/helper-wrap-function": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.13.0.tgz", - "integrity": "sha512-1UX9F7K3BS42fI6qd2A4BjKzgGjToscyZTdp1DjknHLCIvpgne6918io+aL5LXFcER/8QWiwpoY902pVEqgTXA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.5.tgz", + "integrity": "sha512-2J2pmLBqUqVdJw78U0KPNdeE2qeuIyKoG4mKV7wAq3mc4jJG282UgjZw4ZYDnqiWQuS3Y3IYdF/AQ6CpyBV3VA==", "requires": { - "@babel/helper-function-name": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.13.0", - "@babel/types": "^7.13.0" + "@babel/helper-function-name": "^7.16.0", + "@babel/template": "^7.16.0", + "@babel/traverse": "^7.16.5", + "@babel/types": "^7.16.0" } }, "@babel/helpers": { - "version": "7.13.17", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.13.17.tgz", - "integrity": "sha512-Eal4Gce4kGijo1/TGJdqp3WuhllaMLSrW6XcL0ulyUAQOuxHcCafZE8KHg9857gcTehsm/v7RcOx2+jp0Ryjsg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.5.tgz", + "integrity": "sha512-TLgi6Lh71vvMZGEkFuIxzaPsyeYCHQ5jJOOX1f0xXn0uciFuE8cEk0wyBquMcCxBXZ5BJhE2aUB7pnWTD150Tw==", "requires": { - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.13.17", - "@babel/types": "^7.13.17" + "@babel/template": "^7.16.0", + "@babel/traverse": "^7.16.5", + "@babel/types": "^7.16.0" } }, "@babel/highlight": { - "version": "7.13.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz", - "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", + "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", "requires": { - "@babel/helper-validator-identifier": "^7.12.11", + "@babel/helper-validator-identifier": "^7.15.7", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.16.tgz", - "integrity": "sha512-6bAg36mCwuqLO0hbR+z7PHuqWiCeP7Dzg73OpQwsAB1Eb8HnGEz5xYBzCfbu+YjoaJsJs+qheDxVAuqbt3ILEw==" + "version": "7.16.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.6.tgz", + "integrity": "sha512-Gr86ujcNuPDnNOY8mi383Hvi8IYrJVJYuf3XcuBM/Dgd+bINn/7tHqsj+tKkoreMbmGsFLsltI/JJd8fOFWGDQ==" + }, + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.16.2", + "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.16.2.tgz", + "integrity": "sha512-h37CvpLSf8gb2lIJ2CgC3t+EjFbi0t8qS7LCS1xcJIlEXE4czlofwaW7W1HA8zpgOCzI9C1nmoqNR1zWkk0pQg==", + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } }, "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.13.12.tgz", - "integrity": "sha512-d0u3zWKcoZf379fOeJdr1a5WPDny4aOFZ6hlfKivgK0LY7ZxNfoaHL2fWwdGtHyVvra38FC+HVYkO+byfSA8AQ==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.0.tgz", + "integrity": "sha512-4tcFwwicpWTrpl9qjf7UsoosaArgImF85AxqCRZlgc3IQDvkUHjJpruXAL58Wmj+T6fypWTC/BakfEkwIL/pwA==", "requires": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", - "@babel/plugin-proposal-optional-chaining": "^7.13.12" + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", + "@babel/plugin-proposal-optional-chaining": "^7.16.0" } }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.13.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.13.15.tgz", - "integrity": "sha512-VapibkWzFeoa6ubXy/NgV5U2U4MVnUlvnx6wo1XhlsaTrLYWE0UFpDQsVrmn22q5CzeloqJ8gEMHSKxuee6ZdA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.5.tgz", + "integrity": "sha512-C/FX+3HNLV6sz7AqbTQqEo1L9/kfrKjxcVtgyBCmvIgOjvuBVUWooDoi7trsLxOzCEo5FccjRvKHkfDsJFZlfA==", "requires": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-remap-async-to-generator": "^7.13.0", + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-remap-async-to-generator": "^7.16.5", "@babel/plugin-syntax-async-generators": "^7.8.4" } }, "@babel/plugin-proposal-class-properties": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.13.0.tgz", - "integrity": "sha512-KnTDjFNC1g+45ka0myZNvSBFLhNCLN+GeGYLDEA8Oq7MZ6yMgfLoIRh86GRT0FjtJhZw8JyUskP9uvj5pHM9Zg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.5.tgz", + "integrity": "sha512-pJD3HjgRv83s5dv1sTnDbZOaTjghKEz8KUn1Kbh2eAIRhGuyQ1XSeI4xVXU3UlIEVA3DAyIdxqT1eRn7Wcn55A==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5" + } + }, + "@babel/plugin-proposal-class-static-block": { + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.16.5.tgz", + "integrity": "sha512-EEFzuLZcm/rNJ8Q5krK+FRKdVkd6FjfzT9tuSZql9sQn64K0hHA2KLJ0DqVot9/iV6+SsuadC5yI39zWnm+nmQ==", "requires": { - "@babel/helper-create-class-features-plugin": "^7.13.0", - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-create-class-features-plugin": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" } }, "@babel/plugin-proposal-decorators": { - "version": "7.13.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.13.15.tgz", - "integrity": "sha512-ibAMAqUm97yzi+LPgdr5Nqb9CMkeieGHvwPg1ywSGjZrZHQEGqE01HmOio8kxRpA/+VtOHouIVy2FMpBbtltjA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.16.5.tgz", + "integrity": "sha512-XAiZll5oCdp2Dd2RbXA3LVPlFyIRhhcQy+G34p9ePpl6mjFkbqHAYHovyw2j5mqUrlBf0/+MtOIJ3JGYtz8qaw==", "requires": { - "@babel/helper-create-class-features-plugin": "^7.13.11", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-decorators": "^7.12.13" + "@babel/helper-create-class-features-plugin": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/plugin-syntax-decorators": "^7.16.5" } }, "@babel/plugin-proposal-dynamic-import": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.13.8.tgz", - "integrity": "sha512-ONWKj0H6+wIRCkZi9zSbZtE/r73uOhMVHh256ys0UzfM7I3d4n+spZNWjOnJv2gzopumP2Wxi186vI8N0Y2JyQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.5.tgz", + "integrity": "sha512-P05/SJZTTvHz79LNYTF8ff5xXge0kk5sIIWAypcWgX4BTRUgyHc8wRxJ/Hk+mU0KXldgOOslKaeqnhthcDJCJQ==", "requires": { - "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-plugin-utils": "^7.16.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3" } }, "@babel/plugin-proposal-export-namespace-from": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.13.tgz", - "integrity": "sha512-INAgtFo4OnLN3Y/j0VwAgw3HDXcDtX+C/erMvWzuV9v71r7urb6iyMXu7eM9IgLr1ElLlOkaHjJ0SbCmdOQ3Iw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.5.tgz", + "integrity": "sha512-i+sltzEShH1vsVydvNaTRsgvq2vZsfyrd7K7vPLUU/KgS0D5yZMe6uipM0+izminnkKrEfdUnz7CxMRb6oHZWw==", "requires": { - "@babel/helper-plugin-utils": "^7.12.13", + "@babel/helper-plugin-utils": "^7.16.5", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" } }, "@babel/plugin-proposal-json-strings": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.13.8.tgz", - "integrity": "sha512-w4zOPKUFPX1mgvTmL/fcEqy34hrQ1CRcGxdphBc6snDnnqJ47EZDIyop6IwXzAC8G916hsIuXB2ZMBCExC5k7Q==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.5.tgz", + "integrity": "sha512-QQJueTFa0y9E4qHANqIvMsuxM/qcLQmKttBACtPCQzGUEizsXDACGonlPiSwynHfOa3vNw0FPMVvQzbuXwh4SQ==", "requires": { - "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-plugin-utils": "^7.16.5", "@babel/plugin-syntax-json-strings": "^7.8.3" } }, "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.13.8.tgz", - "integrity": "sha512-aul6znYB4N4HGweImqKn59Su9RS8lbUIqxtXTOcAGtNIDczoEFv+l1EhmX8rUBp3G1jMjKJm8m0jXVp63ZpS4A==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.5.tgz", + "integrity": "sha512-xqibl7ISO2vjuQM+MzR3rkd0zfNWltk7n9QhaD8ghMmMceVguYrNDt7MikRyj4J4v3QehpnrU8RYLnC7z/gZLA==", "requires": { - "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-plugin-utils": "^7.16.5", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" } }, "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.13.8.tgz", - "integrity": "sha512-iePlDPBn//UhxExyS9KyeYU7RM9WScAG+D3Hhno0PLJebAEpDZMocbDe64eqynhNAnwz/vZoL/q/QB2T1OH39A==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.5.tgz", + "integrity": "sha512-YwMsTp/oOviSBhrjwi0vzCUycseCYwoXnLiXIL3YNjHSMBHicGTz7GjVU/IGgz4DtOEXBdCNG72pvCX22ehfqg==", "requires": { - "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-plugin-utils": "^7.16.5", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" } }, "@babel/plugin-proposal-numeric-separator": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.13.tgz", - "integrity": "sha512-O1jFia9R8BUCl3ZGB7eitaAPu62TXJRHn7rh+ojNERCFyqRwJMTmhz+tJ+k0CwI6CLjX/ee4qW74FSqlq9I35w==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.5.tgz", + "integrity": "sha512-DvB9l/TcsCRvsIV9v4jxR/jVP45cslTVC0PMVHvaJhhNuhn2Y1SOhCSFlPK777qLB5wb8rVDaNoqMTyOqtY5Iw==", "requires": { - "@babel/helper-plugin-utils": "^7.12.13", + "@babel/helper-plugin-utils": "^7.16.5", "@babel/plugin-syntax-numeric-separator": "^7.10.4" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.13.8.tgz", - "integrity": "sha512-DhB2EuB1Ih7S3/IRX5AFVgZ16k3EzfRbq97CxAVI1KSYcW+lexV8VZb7G7L8zuPVSdQMRn0kiBpf/Yzu9ZKH0g==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.16.5.tgz", + "integrity": "sha512-UEd6KpChoyPhCoE840KRHOlGhEZFutdPDMGj+0I56yuTTOaT51GzmnEl/0uT41fB/vD2nT+Pci2KjezyE3HmUw==", "requires": { - "@babel/compat-data": "^7.13.8", - "@babel/helper-compilation-targets": "^7.13.8", - "@babel/helper-plugin-utils": "^7.13.0", + "@babel/compat-data": "^7.16.4", + "@babel/helper-compilation-targets": "^7.16.3", + "@babel/helper-plugin-utils": "^7.16.5", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.13.0" + "@babel/plugin-transform-parameters": "^7.16.5" } }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.13.8.tgz", - "integrity": "sha512-0wS/4DUF1CuTmGo+NiaHfHcVSeSLj5S3e6RivPTg/2k3wOv3jO35tZ6/ZWsQhQMvdgI7CwphjQa/ccarLymHVA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.5.tgz", + "integrity": "sha512-ihCMxY1Iljmx4bWy/PIMJGXN4NS4oUj1MKynwO07kiKms23pNvIn1DMB92DNB2R0EA882sw0VXIelYGdtF7xEQ==", "requires": { - "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-plugin-utils": "^7.16.5", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" } }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.13.12.tgz", - "integrity": "sha512-fcEdKOkIB7Tf4IxrgEVeFC4zeJSTr78no9wTdBuZZbqF64kzllU0ybo2zrzm7gUQfxGhBgq4E39oRs8Zx/RMYQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.5.tgz", + "integrity": "sha512-kzdHgnaXRonttiTfKYnSVafbWngPPr2qKw9BWYBESl91W54e+9R5pP70LtWxV56g0f05f/SQrwHYkfvbwcdQ/A==", "requires": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", "@babel/plugin-syntax-optional-chaining": "^7.8.3" } }, "@babel/plugin-proposal-private-methods": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.13.0.tgz", - "integrity": "sha512-MXyyKQd9inhx1kDYPkFRVOBXQ20ES8Pto3T7UZ92xj2mY0EVD8oAVzeyYuVfy/mxAdTSIayOvg+aVzcHV2bn6Q==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.5.tgz", + "integrity": "sha512-+yFMO4BGT3sgzXo+lrq7orX5mAZt57DwUK6seqII6AcJnJOIhBJ8pzKH47/ql/d426uQ7YhN8DpUFirQzqYSUA==", "requires": { - "@babel/helper-create-class-features-plugin": "^7.13.0", - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-create-class-features-plugin": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5" + } + }, + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.5.tgz", + "integrity": "sha512-+YGh5Wbw0NH3y/E5YMu6ci5qTDmAEVNoZ3I54aB6nVEOZ5BQ7QJlwKq5pYVucQilMByGn/bvX0af+uNaPRCabA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-create-class-features-plugin": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" } }, "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.13.tgz", - "integrity": "sha512-XyJmZidNfofEkqFV5VC/bLabGmO5QzenPO/YOfGuEbgU+2sSwMmio3YLb4WtBgcmmdwZHyVyv8on77IUjQ5Gvg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.5.tgz", + "integrity": "sha512-s5sKtlKQyFSatt781HQwv1hoM5BQ9qRH30r+dK56OLDsHmV74mzwJNX7R1yMuE7VZKG5O6q/gmOGSAO6ikTudg==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-create-regexp-features-plugin": "^7.16.0", + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-syntax-async-generators": { @@ -13817,12 +14386,20 @@ "@babel/helper-plugin-utils": "^7.12.13" } }, + "@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==", + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, "@babel/plugin-syntax-decorators": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.12.13.tgz", - "integrity": "sha512-Rw6aIXGuqDLr6/LoBBYE57nKOzQpz/aDkKlMqEwH+Vp0MXbG6H/TfRjaY343LKxzAKAMXIHsQ8JzaZKuDZ9MwA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.16.5.tgz", + "integrity": "sha512-3CbYTXfflvyy8O819uhZcZSMedZG4J8yS/NLTc/8T24M9ke1GssTGvg8VZu3Yn2LU5IyQSv1CmPq0a9JWHXJwg==", "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-syntax-dynamic-import": { @@ -13850,11 +14427,11 @@ } }, "@babel/plugin-syntax-jsx": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.13.tgz", - "integrity": "sha512-d4HM23Q1K7oq/SLNmG6mRt85l2csmQ0cHRaxRXjKW0YFdEXqlZ5kzFQKH5Uc3rDJECgu+yCRgPkG04Mm98R/1g==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.5.tgz", + "integrity": "sha512-42OGssv9NPk4QHKVgIHlzeLgPOW5rGgfV5jzG90AhcXXIv6hu/eqj63w4VgvRxdvZY3AlYeDgPiSJ3BqAd1Y6Q==", "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-syntax-logical-assignment-operators": { @@ -13905,332 +14482,345 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, + "@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==", + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, "@babel/plugin-syntax-top-level-await": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.13.tgz", - "integrity": "sha512-A81F9pDwyS7yM//KwbCSDqy3Uj4NMIurtplxphWxoYtNPov7cJsDkAFNNyVlIZ3jwGycVsurZ+LtOA8gZ376iQ==", + "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==", "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.14.5" } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.13.0.tgz", - "integrity": "sha512-96lgJagobeVmazXFaDrbmCLQxBysKu7U6Do3mLsx27gf5Dk85ezysrs2BZUpXD703U/Su1xTBDxxar2oa4jAGg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.5.tgz", + "integrity": "sha512-8bTHiiZyMOyfZFULjsCnYOWG059FVMes0iljEHSfARhNgFfpsqE92OrCffv3veSw9rwMkYcFe9bj0ZoXU2IGtQ==", "requires": { - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.13.0.tgz", - "integrity": "sha512-3j6E004Dx0K3eGmhxVJxwwI89CTJrce7lg3UrtFuDAVQ/2+SJ/h/aSFOeE6/n0WB1GsOffsJp6MnPQNQ8nmwhg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.5.tgz", + "integrity": "sha512-TMXgfioJnkXU+XRoj7P2ED7rUm5jbnDWwlCuFVTpQboMfbSya5WrmubNBAMlk7KXvywpo8rd8WuYZkis1o2H8w==", "requires": { - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-remap-async-to-generator": "^7.13.0" + "@babel/helper-module-imports": "^7.16.0", + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-remap-async-to-generator": "^7.16.5" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.13.tgz", - "integrity": "sha512-zNyFqbc3kI/fVpqwfqkg6RvBgFpC4J18aKKMmv7KdQ/1GgREapSJAykLMVNwfRGO3BtHj3YQZl8kxCXPcVMVeg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.5.tgz", + "integrity": "sha512-BxmIyKLjUGksJ99+hJyL/HIxLIGnLKtw772zYDER7UuycDZ+Xvzs98ZQw6NGgM2ss4/hlFAaGiZmMNKvValEjw==", "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.13.16.tgz", - "integrity": "sha512-ad3PHUxGnfWF4Efd3qFuznEtZKoBp0spS+DgqzVzRPV7urEBvPLue3y2j80w4Jf2YLzZHj8TOv/Lmvdmh3b2xg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.5.tgz", + "integrity": "sha512-JxjSPNZSiOtmxjX7PBRBeRJTUKTyJ607YUYeT0QJCNdsedOe+/rXITjP08eG8xUpsLfPirgzdCFN+h0w6RI+pQ==", "requires": { - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-classes": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.13.0.tgz", - "integrity": "sha512-9BtHCPUARyVH1oXGcSJD3YpsqRLROJx5ZNP6tN5vnk17N0SVf9WCtf8Nuh1CFmgByKKAIMstitKduoCmsaDK5g==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.12.13", - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-replace-supers": "^7.13.0", - "@babel/helper-split-export-declaration": "^7.12.13", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.5.tgz", + "integrity": "sha512-DzJ1vYf/7TaCYy57J3SJ9rV+JEuvmlnvvyvYKFbk5u46oQbBvuB9/0w+YsVsxkOv8zVWKpDmUoj4T5ILHoXevA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-environment-visitor": "^7.16.5", + "@babel/helper-function-name": "^7.16.0", + "@babel/helper-optimise-call-expression": "^7.16.0", + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-replace-supers": "^7.16.5", + "@babel/helper-split-export-declaration": "^7.16.0", "globals": "^11.1.0" } }, "@babel/plugin-transform-computed-properties": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.13.0.tgz", - "integrity": "sha512-RRqTYTeZkZAz8WbieLTvKUEUxZlUTdmL5KGMyZj7FnMfLNKV4+r5549aORG/mgojRmFlQMJDUupwAMiF2Q7OUg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.5.tgz", + "integrity": "sha512-n1+O7xtU5lSLraRzX88CNcpl7vtGdPakKzww74bVwpAIRgz9JVLJJpOLb0uYqcOaXVM0TL6X0RVeIJGD2CnCkg==", "requires": { - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-destructuring": { - "version": "7.13.17", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.13.17.tgz", - "integrity": "sha512-UAUqiLv+uRLO+xuBKKMEpC+t7YRNVRqBsWWq1yKXbBZBje/t3IXCiSinZhjn/DC3qzBfICeYd2EFGEbHsh5RLA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.16.5.tgz", + "integrity": "sha512-GuRVAsjq+c9YPK6NeTkRLWyQskDC099XkBSVO+6QzbnOnH2d/4mBVXYStaPrZD3dFRfg00I6BFJ9Atsjfs8mlg==", "requires": { - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.13.tgz", - "integrity": "sha512-foDrozE65ZFdUC2OfgeOCrEPTxdB3yjqxpXh8CH+ipd9CHd4s/iq81kcUpyH8ACGNEPdFqbtzfgzbT/ZGlbDeQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.5.tgz", + "integrity": "sha512-iQiEMt8Q4/5aRGHpGVK2Zc7a6mx7qEAO7qehgSug3SDImnuMzgmm/wtJALXaz25zUj1PmnNHtShjFgk4PDx4nw==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-create-regexp-features-plugin": "^7.16.0", + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.13.tgz", - "integrity": "sha512-NfADJiiHdhLBW3pulJlJI2NB0t4cci4WTZ8FtdIuNc2+8pslXdPtRRAEWqUY+m9kNOk2eRYbTAOipAxlrOcwwQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.5.tgz", + "integrity": "sha512-81tijpDg2a6I1Yhj4aWY1l3O1J4Cg/Pd7LfvuaH2VVInAkXtzibz9+zSPdUM1WvuUi128ksstAP0hM5w48vQgg==", "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.13.tgz", - "integrity": "sha512-fbUelkM1apvqez/yYx1/oICVnGo2KM5s63mhGylrmXUxK/IAXSIf87QIxVfZldWf4QsOafY6vV3bX8aMHSvNrA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.5.tgz", + "integrity": "sha512-12rba2HwemQPa7BLIKCzm1pT2/RuQHtSFHdNl41cFiC6oi4tcrp7gjB07pxQvFpcADojQywSjblQth6gJyE6CA==", "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-for-of": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.13.0.tgz", - "integrity": "sha512-IHKT00mwUVYE0zzbkDgNRP6SRzvfGCYsOxIRz8KsiaaHCcT9BWIkO+H9QRJseHBLOGBZkHUdHiqj6r0POsdytg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.5.tgz", + "integrity": "sha512-+DpCAJFPAvViR17PIMi9x2AE34dll5wNlXO43wagAX2YcRGgEVHCNFC4azG85b4YyyFarvkc/iD5NPrz4Oneqw==", "requires": { - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-function-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.13.tgz", - "integrity": "sha512-6K7gZycG0cmIwwF7uMK/ZqeCikCGVBdyP2J5SKNCXO5EOHcqi+z7Jwf8AmyDNcBgxET8DrEtCt/mPKPyAzXyqQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.5.tgz", + "integrity": "sha512-Fuec/KPSpVLbGo6z1RPw4EE1X+z9gZk1uQmnYy7v4xr4TO9p41v1AoUuXEtyqAI7H+xNJYSICzRqZBhDEkd3kQ==", "requires": { - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-function-name": "^7.16.0", + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-literals": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.13.tgz", - "integrity": "sha512-FW+WPjSR7hiUxMcKqyNjP05tQ2kmBCdpEpZHY1ARm96tGQCCBvXKnpjILtDplUnJ/eHZ0lALLM+d2lMFSpYJrQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.5.tgz", + "integrity": "sha512-B1j9C/IfvshnPcklsc93AVLTrNVa69iSqztylZH6qnmiAsDDOmmjEYqOm3Ts2lGSgTSywnBNiqC949VdD0/gfw==", "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-member-expression-literals": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.13.tgz", - "integrity": "sha512-kxLkOsg8yir4YeEPHLuO2tXP9R/gTjpuTOjshqSpELUN3ZAg2jfDnKUvzzJxObun38sw3wm4Uu69sX/zA7iRvg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.5.tgz", + "integrity": "sha512-d57i3vPHWgIde/9Y8W/xSFUndhvhZN5Wu2TjRrN1MVz5KzdUihKnfDVlfP1U7mS5DNj/WHHhaE4/tTi4hIyHwQ==", "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.13.0.tgz", - "integrity": "sha512-EKy/E2NHhY/6Vw5d1k3rgoobftcNUmp9fGjb9XZwQLtTctsRBOTRO7RHHxfIky1ogMN5BxN7p9uMA3SzPfotMQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.5.tgz", + "integrity": "sha512-oHI15S/hdJuSCfnwIz+4lm6wu/wBn7oJ8+QrkzPPwSFGXk8kgdI/AIKcbR/XnD1nQVMg/i6eNaXpszbGuwYDRQ==", "requires": { - "@babel/helper-module-transforms": "^7.13.0", - "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-module-transforms": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.13.8.tgz", - "integrity": "sha512-9QiOx4MEGglfYZ4XOnU79OHr6vIWUakIj9b4mioN8eQIoEh+pf5p/zEB36JpDFWA12nNMiRf7bfoRvl9Rn79Bw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.5.tgz", + "integrity": "sha512-ABhUkxvoQyqhCWyb8xXtfwqNMJD7tx+irIRnUh6lmyFud7Jln1WzONXKlax1fg/ey178EXbs4bSGNd6PngO+SQ==", "requires": { - "@babel/helper-module-transforms": "^7.13.0", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-simple-access": "^7.12.13", + "@babel/helper-module-transforms": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-simple-access": "^7.16.0", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.13.8.tgz", - "integrity": "sha512-hwqctPYjhM6cWvVIlOIe27jCIBgHCsdH2xCJVAYQm7V5yTMoilbVMi9f6wKg0rpQAOn6ZG4AOyvCqFF/hUh6+A==", - "requires": { - "@babel/helper-hoist-variables": "^7.13.0", - "@babel/helper-module-transforms": "^7.13.0", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-validator-identifier": "^7.12.11", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.16.5.tgz", + "integrity": "sha512-53gmLdScNN28XpjEVIm7LbWnD/b/TpbwKbLk6KV4KqC9WyU6rq1jnNmVG6UgAdQZVVGZVoik3DqHNxk4/EvrjA==", + "requires": { + "@babel/helper-hoist-variables": "^7.16.0", + "@babel/helper-module-transforms": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-validator-identifier": "^7.15.7", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-umd": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.13.0.tgz", - "integrity": "sha512-D/ILzAh6uyvkWjKKyFE/W0FzWwasv6vPTSqPcjxFqn6QpX3u8DjRVliq4F2BamO2Wee/om06Vyy+vPkNrd4wxw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.5.tgz", + "integrity": "sha512-qTFnpxHMoenNHkS3VoWRdwrcJ3FhX567GvDA3hRZKF0Dj8Fmg0UzySZp3AP2mShl/bzcywb/UWAMQIjA1bhXvw==", "requires": { - "@babel/helper-module-transforms": "^7.13.0", - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-module-transforms": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.13.tgz", - "integrity": "sha512-Xsm8P2hr5hAxyYblrfACXpQKdQbx4m2df9/ZZSQ8MAhsadw06+jW7s9zsSw6he+mJZXRlVMyEnVktJo4zjk1WA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.5.tgz", + "integrity": "sha512-/wqGDgvFUeKELW6ex6QB7dLVRkd5ehjw34tpXu1nhKC0sFfmaLabIswnpf8JgDyV2NeDmZiwoOb0rAmxciNfjA==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.13" + "@babel/helper-create-regexp-features-plugin": "^7.16.0" } }, "@babel/plugin-transform-new-target": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.13.tgz", - "integrity": "sha512-/KY2hbLxrG5GTQ9zzZSc3xWiOy379pIETEhbtzwZcw9rvuaVV4Fqy7BYGYOWZnaoXIQYbbJ0ziXLa/sKcGCYEQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.5.tgz", + "integrity": "sha512-ZaIrnXF08ZC8jnKR4/5g7YakGVL6go6V9ql6Jl3ecO8PQaQqFE74CuM384kezju7Z9nGCCA20BqZaR1tJ/WvHg==", "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-object-super": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.13.tgz", - "integrity": "sha512-JzYIcj3XtYspZDV8j9ulnoMPZZnF/Cj0LUxPOjR89BdBVx+zYJI9MdMIlUZjbXDX+6YVeS6I3e8op+qQ3BYBoQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.5.tgz", + "integrity": "sha512-tded+yZEXuxt9Jdtkc1RraW1zMF/GalVxaVVxh41IYwirdRgyAxxxCKZ9XB7LxZqmsjfjALxupNE1MIz9KH+Zg==", "requires": { - "@babel/helper-plugin-utils": "^7.12.13", - "@babel/helper-replace-supers": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-replace-supers": "^7.16.5" } }, "@babel/plugin-transform-parameters": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.13.0.tgz", - "integrity": "sha512-Jt8k/h/mIwE2JFEOb3lURoY5C85ETcYPnbuAJ96zRBzh1XHtQZfs62ChZ6EP22QlC8c7Xqr9q+e1SU5qttwwjw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.5.tgz", + "integrity": "sha512-B3O6AL5oPop1jAVg8CV+haeUte9oFuY85zu0jwnRNZZi3tVAbJriu5tag/oaO2kGaQM/7q7aGPBlTI5/sr9enA==", "requires": { - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-property-literals": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.13.tgz", - "integrity": "sha512-nqVigwVan+lR+g8Fj8Exl0UQX2kymtjcWfMOYM1vTYEKujeyv2SkMgazf2qNcK7l4SDiKyTA/nHCPqL4e2zo1A==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.5.tgz", + "integrity": "sha512-+IRcVW71VdF9pEH/2R/Apab4a19LVvdVsr/gEeotH00vSDVlKD+XgfSIw+cgGWsjDB/ziqGv/pGoQZBIiQVXHg==", "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-regenerator": { - "version": "7.13.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.13.15.tgz", - "integrity": "sha512-Bk9cOLSz8DiurcMETZ8E2YtIVJbFCPGW28DJWUakmyVWtQSm6Wsf0p4B4BfEr/eL2Nkhe/CICiUiMOCi1TPhuQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.5.tgz", + "integrity": "sha512-2z+it2eVWU8TtQQRauvGUqZwLy4+7rTfo6wO4npr+fvvN1SW30ZF3O/ZRCNmTuu4F5MIP8OJhXAhRV5QMJOuYg==", "requires": { "regenerator-transform": "^0.14.2" } }, "@babel/plugin-transform-reserved-words": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.13.tgz", - "integrity": "sha512-xhUPzDXxZN1QfiOy/I5tyye+TRz6lA7z6xaT4CLOjPRMVg1ldRf0LHw0TDBpYL4vG78556WuHdyO9oi5UmzZBg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.5.tgz", + "integrity": "sha512-aIB16u8lNcf7drkhXJRoggOxSTUAuihTSTfAcpynowGJOZiGf+Yvi7RuTwFzVYSYPmWyARsPqUGoZWWWxLiknw==", "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-runtime": { - "version": "7.13.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.13.15.tgz", - "integrity": "sha512-d+ezl76gx6Jal08XngJUkXM4lFXK/5Ikl9Mh4HKDxSfGJXmZ9xG64XT2oivBzfxb/eQ62VfvoMkaCZUKJMVrBA==", - "requires": { - "@babel/helper-module-imports": "^7.13.12", - "@babel/helper-plugin-utils": "^7.13.0", - "babel-plugin-polyfill-corejs2": "^0.2.0", - "babel-plugin-polyfill-corejs3": "^0.2.0", - "babel-plugin-polyfill-regenerator": "^0.2.0", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.16.5.tgz", + "integrity": "sha512-gxpfS8XQWDbQ8oP5NcmpXxtEgCJkbO+W9VhZlOhr0xPyVaRjAQPOv7ZDj9fg0d5s9+NiVvMCE6gbkEkcsxwGRw==", + "requires": { + "@babel/helper-module-imports": "^7.16.0", + "@babel/helper-plugin-utils": "^7.16.5", + "babel-plugin-polyfill-corejs2": "^0.3.0", + "babel-plugin-polyfill-corejs3": "^0.4.0", + "babel-plugin-polyfill-regenerator": "^0.3.0", "semver": "^6.3.0" } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.13.tgz", - "integrity": "sha512-xpL49pqPnLtf0tVluuqvzWIgLEhuPpZzvs2yabUHSKRNlN7ScYU7aMlmavOeyXJZKgZKQRBlh8rHbKiJDraTSw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.5.tgz", + "integrity": "sha512-ZbuWVcY+MAXJuuW7qDoCwoxDUNClfZxoo7/4swVbOW1s/qYLOMHlm9YRWMsxMFuLs44eXsv4op1vAaBaBaDMVg==", "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-spread": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.13.0.tgz", - "integrity": "sha512-V6vkiXijjzYeFmQTr3dBxPtZYLPcUfY34DebOU27jIl2M/Y8Egm52Hw82CSjjPqd54GTlJs5x+CR7HeNr24ckg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.5.tgz", + "integrity": "sha512-5d6l/cnG7Lw4tGHEoga4xSkYp1euP7LAtrah1h1PgJ3JY7yNsjybsxQAnVK4JbtReZ/8z6ASVmd3QhYYKLaKZw==", "requires": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1" + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.13.tgz", - "integrity": "sha512-Jc3JSaaWT8+fr7GRvQP02fKDsYk4K/lYwWq38r/UGfaxo89ajud321NH28KRQ7xy1Ybc0VUE5Pz8psjNNDUglg==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.5.tgz", + "integrity": "sha512-usYsuO1ID2LXxzuUxifgWtJemP7wL2uZtyrTVM4PKqsmJycdS4U4mGovL5xXkfUheds10Dd2PjoQLXw6zCsCbg==", "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-template-literals": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.13.0.tgz", - "integrity": "sha512-d67umW6nlfmr1iehCcBv69eSUSySk1EsIS8aTDX4Xo9qajAh6mYtcl4kJrBkGXuxZPEgVr7RVfAvNW6YQkd4Mw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.5.tgz", + "integrity": "sha512-gnyKy9RyFhkovex4BjKWL3BVYzUDG6zC0gba7VMLbQoDuqMfJ1SDXs8k/XK41Mmt1Hyp4qNAvGFb9hKzdCqBRQ==", "requires": { - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.13.tgz", - "integrity": "sha512-eKv/LmUJpMnu4npgfvs3LiHhJua5fo/CysENxa45YCQXZwKnGCQKAg87bvoqSW1fFT+HA32l03Qxsm8ouTY3ZQ==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.5.tgz", + "integrity": "sha512-ldxCkW180qbrvyCVDzAUZqB0TAeF8W/vGJoRcaf75awm6By+PxfJKvuqVAnq8N9wz5Xa6mSpM19OfVKKVmGHSQ==", "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-unicode-escapes": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.13.tgz", - "integrity": "sha512-0bHEkdwJ/sN/ikBHfSmOXPypN/beiGqjo+o4/5K+vxEFNPRPdImhviPakMKG4x96l85emoa0Z6cDflsdBusZbw==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.5.tgz", + "integrity": "sha512-shiCBHTIIChGLdyojsKQjoAyB8MBwat25lKM7MJjbe1hE0bgIppD+LX9afr41lLHOhqceqeWl4FkLp+Bgn9o1Q==", "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.13.tgz", - "integrity": "sha512-mDRzSNY7/zopwisPZ5kM9XKCfhchqIYwAKRERtEnhYscZB79VRekuRSoYbN0+KVe3y8+q1h6A4svXtP7N+UoCA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.5.tgz", + "integrity": "sha512-GTJ4IW012tiPEMMubd7sD07iU9O/LOo8Q/oU4xNhcaq0Xn8+6TcUQaHtC8YxySo1T+ErQ8RaWogIEeFhKGNPzw==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-create-regexp-features-plugin": "^7.16.0", + "@babel/helper-plugin-utils": "^7.16.5" } }, "@babel/preset-env": { - "version": "7.13.15", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.13.15.tgz", - "integrity": "sha512-D4JAPMXcxk69PKe81jRJ21/fP/uYdcTZ3hJDF5QX2HSI9bBxxYw/dumdR6dGumhjxlprHPE4XWoPaqzZUVy2MA==", - "requires": { - "@babel/compat-data": "^7.13.15", - "@babel/helper-compilation-targets": "^7.13.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-validator-option": "^7.12.17", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.13.12", - "@babel/plugin-proposal-async-generator-functions": "^7.13.15", - "@babel/plugin-proposal-class-properties": "^7.13.0", - "@babel/plugin-proposal-dynamic-import": "^7.13.8", - "@babel/plugin-proposal-export-namespace-from": "^7.12.13", - "@babel/plugin-proposal-json-strings": "^7.13.8", - "@babel/plugin-proposal-logical-assignment-operators": "^7.13.8", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", - "@babel/plugin-proposal-numeric-separator": "^7.12.13", - "@babel/plugin-proposal-object-rest-spread": "^7.13.8", - "@babel/plugin-proposal-optional-catch-binding": "^7.13.8", - "@babel/plugin-proposal-optional-chaining": "^7.13.12", - "@babel/plugin-proposal-private-methods": "^7.13.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.12.13", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.16.5.tgz", + "integrity": "sha512-MiJJW5pwsktG61NDxpZ4oJ1CKxM1ncam9bzRtx9g40/WkLRkxFP6mhpkYV0/DxcciqoiHicx291+eUQrXb/SfQ==", + "requires": { + "@babel/compat-data": "^7.16.4", + "@babel/helper-compilation-targets": "^7.16.3", + "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-validator-option": "^7.14.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.16.2", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.16.0", + "@babel/plugin-proposal-async-generator-functions": "^7.16.5", + "@babel/plugin-proposal-class-properties": "^7.16.5", + "@babel/plugin-proposal-class-static-block": "^7.16.5", + "@babel/plugin-proposal-dynamic-import": "^7.16.5", + "@babel/plugin-proposal-export-namespace-from": "^7.16.5", + "@babel/plugin-proposal-json-strings": "^7.16.5", + "@babel/plugin-proposal-logical-assignment-operators": "^7.16.5", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.5", + "@babel/plugin-proposal-numeric-separator": "^7.16.5", + "@babel/plugin-proposal-object-rest-spread": "^7.16.5", + "@babel/plugin-proposal-optional-catch-binding": "^7.16.5", + "@babel/plugin-proposal-optional-chaining": "^7.16.5", + "@babel/plugin-proposal-private-methods": "^7.16.5", + "@babel/plugin-proposal-private-property-in-object": "^7.16.5", + "@babel/plugin-proposal-unicode-property-regex": "^7.16.5", "@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-json-strings": "^7.8.3", @@ -14240,52 +14830,53 @@ "@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-top-level-await": "^7.12.13", - "@babel/plugin-transform-arrow-functions": "^7.13.0", - "@babel/plugin-transform-async-to-generator": "^7.13.0", - "@babel/plugin-transform-block-scoped-functions": "^7.12.13", - "@babel/plugin-transform-block-scoping": "^7.12.13", - "@babel/plugin-transform-classes": "^7.13.0", - "@babel/plugin-transform-computed-properties": "^7.13.0", - "@babel/plugin-transform-destructuring": "^7.13.0", - "@babel/plugin-transform-dotall-regex": "^7.12.13", - "@babel/plugin-transform-duplicate-keys": "^7.12.13", - "@babel/plugin-transform-exponentiation-operator": "^7.12.13", - "@babel/plugin-transform-for-of": "^7.13.0", - "@babel/plugin-transform-function-name": "^7.12.13", - "@babel/plugin-transform-literals": "^7.12.13", - "@babel/plugin-transform-member-expression-literals": "^7.12.13", - "@babel/plugin-transform-modules-amd": "^7.13.0", - "@babel/plugin-transform-modules-commonjs": "^7.13.8", - "@babel/plugin-transform-modules-systemjs": "^7.13.8", - "@babel/plugin-transform-modules-umd": "^7.13.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.12.13", - "@babel/plugin-transform-new-target": "^7.12.13", - "@babel/plugin-transform-object-super": "^7.12.13", - "@babel/plugin-transform-parameters": "^7.13.0", - "@babel/plugin-transform-property-literals": "^7.12.13", - "@babel/plugin-transform-regenerator": "^7.13.15", - "@babel/plugin-transform-reserved-words": "^7.12.13", - "@babel/plugin-transform-shorthand-properties": "^7.12.13", - "@babel/plugin-transform-spread": "^7.13.0", - "@babel/plugin-transform-sticky-regex": "^7.12.13", - "@babel/plugin-transform-template-literals": "^7.13.0", - "@babel/plugin-transform-typeof-symbol": "^7.12.13", - "@babel/plugin-transform-unicode-escapes": "^7.12.13", - "@babel/plugin-transform-unicode-regex": "^7.12.13", - "@babel/preset-modules": "^0.1.4", - "@babel/types": "^7.13.14", - "babel-plugin-polyfill-corejs2": "^0.2.0", - "babel-plugin-polyfill-corejs3": "^0.2.0", - "babel-plugin-polyfill-regenerator": "^0.2.0", - "core-js-compat": "^3.9.0", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.16.5", + "@babel/plugin-transform-async-to-generator": "^7.16.5", + "@babel/plugin-transform-block-scoped-functions": "^7.16.5", + "@babel/plugin-transform-block-scoping": "^7.16.5", + "@babel/plugin-transform-classes": "^7.16.5", + "@babel/plugin-transform-computed-properties": "^7.16.5", + "@babel/plugin-transform-destructuring": "^7.16.5", + "@babel/plugin-transform-dotall-regex": "^7.16.5", + "@babel/plugin-transform-duplicate-keys": "^7.16.5", + "@babel/plugin-transform-exponentiation-operator": "^7.16.5", + "@babel/plugin-transform-for-of": "^7.16.5", + "@babel/plugin-transform-function-name": "^7.16.5", + "@babel/plugin-transform-literals": "^7.16.5", + "@babel/plugin-transform-member-expression-literals": "^7.16.5", + "@babel/plugin-transform-modules-amd": "^7.16.5", + "@babel/plugin-transform-modules-commonjs": "^7.16.5", + "@babel/plugin-transform-modules-systemjs": "^7.16.5", + "@babel/plugin-transform-modules-umd": "^7.16.5", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.16.5", + "@babel/plugin-transform-new-target": "^7.16.5", + "@babel/plugin-transform-object-super": "^7.16.5", + "@babel/plugin-transform-parameters": "^7.16.5", + "@babel/plugin-transform-property-literals": "^7.16.5", + "@babel/plugin-transform-regenerator": "^7.16.5", + "@babel/plugin-transform-reserved-words": "^7.16.5", + "@babel/plugin-transform-shorthand-properties": "^7.16.5", + "@babel/plugin-transform-spread": "^7.16.5", + "@babel/plugin-transform-sticky-regex": "^7.16.5", + "@babel/plugin-transform-template-literals": "^7.16.5", + "@babel/plugin-transform-typeof-symbol": "^7.16.5", + "@babel/plugin-transform-unicode-escapes": "^7.16.5", + "@babel/plugin-transform-unicode-regex": "^7.16.5", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.16.0", + "babel-plugin-polyfill-corejs2": "^0.3.0", + "babel-plugin-polyfill-corejs3": "^0.4.0", + "babel-plugin-polyfill-regenerator": "^0.3.0", + "core-js-compat": "^3.19.1", "semver": "^6.3.0" } }, "@babel/preset-modules": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", - "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", "requires": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", @@ -14295,42 +14886,44 @@ } }, "@babel/runtime": { - "version": "7.13.17", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.17.tgz", - "integrity": "sha512-NCdgJEelPTSh+FEFylhnP1ylq848l1z9t9N0j1Lfbcw0+KXGjsTvUmkxy+voLLXB5SOKMbLLx4jxYliGrYQseA==", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz", + "integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==", "requires": { "regenerator-runtime": "^0.13.4" } }, "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz", + "integrity": "sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==", "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/code-frame": "^7.16.0", + "@babel/parser": "^7.16.0", + "@babel/types": "^7.16.0" } }, "@babel/traverse": { - "version": "7.13.17", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.17.tgz", - "integrity": "sha512-BMnZn0R+X6ayqm3C3To7o1j7Q020gWdqdyP50KEoVqaCO2c/Im7sYZSmVgvefp8TTMQ+9CtwuBp0Z1CZ8V3Pvg==", - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.13.16", - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.13.16", - "@babel/types": "^7.13.17", + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.5.tgz", + "integrity": "sha512-FOCODAzqUMROikDYLYxl4nmwiLlu85rNqBML/A5hKRVXG2LV8d0iMqgPzdYTcIpjZEBB7D6UDU9vxRZiriASdQ==", + "requires": { + "@babel/code-frame": "^7.16.0", + "@babel/generator": "^7.16.5", + "@babel/helper-environment-visitor": "^7.16.5", + "@babel/helper-function-name": "^7.16.0", + "@babel/helper-hoist-variables": "^7.16.0", + "@babel/helper-split-export-declaration": "^7.16.0", + "@babel/parser": "^7.16.5", + "@babel/types": "^7.16.0", "debug": "^4.1.0", "globals": "^11.1.0" }, "dependencies": { "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "requires": { "ms": "2.1.2" } @@ -14343,11 +14936,11 @@ } }, "@babel/types": { - "version": "7.13.17", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.17.tgz", - "integrity": "sha512-RawydLgxbOPDlTLJNtoIypwdmAy//uQIzlKt2+iBiJaRlVuI6QLUxVAyWGNfOzp8Yu4L4lLIacoCyTNtpb4wiA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", + "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", "requires": { - "@babel/helper-validator-identifier": "^7.12.11", + "@babel/helper-validator-identifier": "^7.15.7", "to-fast-properties": "^2.0.0" } }, @@ -14449,33 +15042,33 @@ } }, "@types/glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", "requires": { "@types/minimatch": "*", "@types/node": "*" } }, "@types/json-schema": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", - "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==" + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==" }, "@types/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==" + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==" }, "@types/node": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.1.tgz", - "integrity": "sha512-TMkXt0Ck1y0KKsGr9gJtWGjttxlZnnvDtphxUOSd0bfaR6Q1jle+sPvrzNR1urqYTWMinoKvjKfXUGsumaO1PA==" + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.2.tgz", + "integrity": "sha512-JepeIUPFDARgIs0zD/SKPgFsJEAF0X5/qO80llx59gOxFTboS9Amv3S+QfB7lqBId5sFXJ99BN0J6zFRvL9dDA==" }, "@types/q": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", - "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==" + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", + "integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==" }, "@vue/babel-helper-vue-jsx-merge-props": { "version": "1.2.1", @@ -14488,9 +15081,9 @@ "integrity": "sha512-hz4R8tS5jMn8lDq6iD+yWL6XNB699pGIVLk7WSJnn1dbpjaazsjZQkieJoRX6gW5zpYSCFqQ7jUquPNY65tQYA==" }, "@vue/babel-plugin-jsx": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.0.5.tgz", - "integrity": "sha512-Jtipy7oI0am5e1q5Ahunm/cCcCh5ssf5VkMQsLR383S3un5Qh7NBfxgSK9kmWf4IXJEhDeYp9kHv8G/EnMai9A==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.1.1.tgz", + "integrity": "sha512-j2uVfZjnB5+zkcbc/zsOc0fSNGCMMjaEXP52wdwdIfn0qjFfEYpYZBFKFg+HHnQeJCVrjOeO0YxgaL7DMrym9w==", "requires": { "@babel/helper-module-imports": "^7.0.0", "@babel/plugin-syntax-jsx": "^7.0.0", @@ -14524,9 +15117,9 @@ } }, "@vue/babel-preset-app": { - "version": "4.5.12", - "resolved": "https://registry.npmjs.org/@vue/babel-preset-app/-/babel-preset-app-4.5.12.tgz", - "integrity": "sha512-8q67ORQ9O0Ms0nlqsXTVhaBefRBaLrzPxOewAZhdcO7onHwcO5/wRdWtHhZgfpCZlhY7NogkU16z3WnorSSkEA==", + "version": "4.5.15", + "resolved": "https://registry.npmjs.org/@vue/babel-preset-app/-/babel-preset-app-4.5.15.tgz", + "integrity": "sha512-J+YttzvwRfV1BPczf8r3qCevznYk+jh531agVF+5EYlHF4Sgh/cGXTz9qkkiux3LQgvhEGXgmCteg1n38WuuKg==", "requires": { "@babel/core": "^7.11.0", "@babel/helper-compilation-targets": "^7.9.6", @@ -14636,17 +15229,17 @@ } }, "@vue/component-compiler-utils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.2.0.tgz", - "integrity": "sha512-lejBLa7xAMsfiZfNp7Kv51zOzifnb29FwdnMLa96z26kXErPFioSf9BMcePVIQ6/Gc6/mC0UrPpxAWIHyae0vw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.3.0.tgz", + "integrity": "sha512-97sfH2mYNU+2PzGrmK2haqffDpVASuib9/w2/noxiFi31Z54hW+q3izKQXXQZSNhtiUpAI36uSuYepeBe4wpHQ==", "requires": { "consolidate": "^0.15.1", "hash-sum": "^1.0.2", "lru-cache": "^4.1.2", "merge-source-map": "^1.1.0", - "postcss": "^7.0.14", + "postcss": "^7.0.36", "postcss-selector-parser": "^6.0.2", - "prettier": "^1.18.2", + "prettier": "^1.18.2 || ^2.0.0", "source-map": "~0.6.1", "vue-template-es2015-compiler": "^1.9.0" }, @@ -14673,17 +15266,17 @@ } }, "@vuepress/core": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/core/-/core-1.8.2.tgz", - "integrity": "sha512-lh9BLC06k9s0wxTuWtCkiNj49fkbW87enp0XSrFZHEoyDGSGndQjZmMMErcHc5Hx7nrW1nzc33sPH1NNtJl0hw==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@vuepress/core/-/core-1.8.3.tgz", + "integrity": "sha512-VY5yfePXt5nf0/wf6RLPXheRrSwXBizW4aYFHE6L59gzwFAJc01iYJ6eW+LDiyX6lb45pWuyZHMdNsMzBgs9xA==", "requires": { "@babel/core": "^7.8.4", "@vue/babel-preset-app": "^4.1.2", - "@vuepress/markdown": "1.8.2", - "@vuepress/markdown-loader": "1.8.2", - "@vuepress/plugin-last-updated": "1.8.2", - "@vuepress/plugin-register-components": "1.8.2", - "@vuepress/shared-utils": "1.8.2", + "@vuepress/markdown": "1.8.3", + "@vuepress/markdown-loader": "1.8.3", + "@vuepress/plugin-last-updated": "1.8.3", + "@vuepress/plugin-register-components": "1.8.3", + "@vuepress/shared-utils": "1.8.3", "autoprefixer": "^9.5.1", "babel-loader": "^8.0.4", "cache-loader": "^3.0.0", @@ -14718,11 +15311,11 @@ } }, "@vuepress/markdown": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/markdown/-/markdown-1.8.2.tgz", - "integrity": "sha512-zznBHVqW+iBkznF/BO/GY9RFu53khyl0Ey0PnGqvwCJpRLNan6y5EXgYumtjw2GSYn5nDTTALYxtyNBdz64PKg==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@vuepress/markdown/-/markdown-1.8.3.tgz", + "integrity": "sha512-gYfMulsHbCS5o/YBoMd6uZDlbiABewxAQq7J71REz7xpWbk1XjPzxYD40VT4xrEYllc/HdQQe49C+34eKHPhMw==", "requires": { - "@vuepress/shared-utils": "1.8.2", + "@vuepress/shared-utils": "1.8.3", "markdown-it": "^8.4.1", "markdown-it-anchor": "^5.0.2", "markdown-it-chain": "^1.3.0", @@ -14759,19 +15352,19 @@ } }, "@vuepress/markdown-loader": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/markdown-loader/-/markdown-loader-1.8.2.tgz", - "integrity": "sha512-mWzFXikCUcAN/chpKkqZpRYKdo0312hMv8cBea2hvrJYV6y4ODB066XKvXN8JwOcxuCjxWYJkhWGr+pXq1oTtw==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@vuepress/markdown-loader/-/markdown-loader-1.8.3.tgz", + "integrity": "sha512-QO3uUszxX63T+iXe7dB/rSDTpcZXrstyBIuI5NtWut/34Qsr/LZvMhrawwLBCtLjkQU1FJ/mlFXkXN8gebHx0g==", "requires": { - "@vuepress/markdown": "1.8.2", + "@vuepress/markdown": "1.8.3", "loader-utils": "^1.1.0", "lru-cache": "^5.1.1" } }, "@vuepress/plugin-active-header-links": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/plugin-active-header-links/-/plugin-active-header-links-1.8.2.tgz", - "integrity": "sha512-JmXAQg8D7J8mcKe2Ue3BZ9dOCzJMJXP4Cnkkc/IrqfDg0ET0l96gYWZohCqlvRIWt4f0VPiFAO4FLYrW+hko+g==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-active-header-links/-/plugin-active-header-links-1.8.3.tgz", + "integrity": "sha512-KNgo0IHUvzoJDAn0xw1HAIUnmnCYhoFCWS9fkgKhfJAKcdaSEZk+BHQKfZrKx1BINktTFNdBBGZmxANQdHlUFQ==", "requires": { "lodash.debounce": "^4.0.8" } @@ -14782,38 +15375,38 @@ "integrity": "sha512-27fQzRMsqGYpMf+ruyhsdfLv/n6z6b6LutFLE/pH66Itlh6ox9ew31x0pqYBbWIC/a4lBfXYUwFvi+DEvlb1EQ==" }, "@vuepress/plugin-last-updated": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/plugin-last-updated/-/plugin-last-updated-1.8.2.tgz", - "integrity": "sha512-pYIRZi52huO9b6HY3JQNPKNERCLzMHejjBRt9ekdnJ1xhLs4MmRvt37BoXjI/qzvXkYtr7nmGgnKThNBVRTZuA==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-last-updated/-/plugin-last-updated-1.8.3.tgz", + "integrity": "sha512-anqktVt3RYyPVC+GxA0gBLWJZKQTXUcH5M+fFiO+tQvSizuhkzY/0i70Ss6znxv8ov5xwVn/C/gBGt21/9Qzvw==", "requires": { "cross-spawn": "^6.0.5" } }, "@vuepress/plugin-nprogress": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/plugin-nprogress/-/plugin-nprogress-1.8.2.tgz", - "integrity": "sha512-3TOBee2NM3WLr1tdjDTGfrAMggjN+OlEPyKyv8FqThsVkDYhw48O3HwqlThp9KX7UbL3ExxIFBwWRFLC+kYrdw==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-nprogress/-/plugin-nprogress-1.8.3.tgz", + "integrity": "sha512-BSMDcON1sIib/JoQh+W4t7WGQeMIFKakZ931IGSeVhLwCmEUd4sgC1dSPFYWXGSxCoattcrFLwqP9gJ9vabJpw==", "requires": { "nprogress": "^0.2.0" } }, "@vuepress/plugin-register-components": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/plugin-register-components/-/plugin-register-components-1.8.2.tgz", - "integrity": "sha512-6SUq3nHFMEh9qKFnjA8QnrNxj0kLs7+Gspq1OBU8vtu0NQmSvLFZVaMV7pzT/9zN2nO5Pld5qhsUJv1g71MrEA==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-register-components/-/plugin-register-components-1.8.3.tgz", + "integrity": "sha512-wp0nI4Eoysb8aHYonyezGV+Jlc2D6B2iEQ7bhSh+UBlBa5K2u9Q1i39He+W4bFwCO7KorfAqvHjRIzHnW095XA==", "requires": { - "@vuepress/shared-utils": "1.8.2" + "@vuepress/shared-utils": "1.8.3" } }, "@vuepress/plugin-search": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/plugin-search/-/plugin-search-1.8.2.tgz", - "integrity": "sha512-JrSJr9o0Kar14lVtZ4wfw39pplxvvMh8vDBD9oW09a+6Zi/4bySPGdcdaqdqGW+OHSiZNvG+6uyfKSBBBqF6PA==" + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-search/-/plugin-search-1.8.3.tgz", + "integrity": "sha512-2ogShgbB5DaqUz6oYjjHHtczNTbOTSkh/9ve1bfYc6f0NasKauQeWUOCOE4lN9RAaP4hapTZ96g2G42VRlVmTg==" }, "@vuepress/shared-utils": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/shared-utils/-/shared-utils-1.8.2.tgz", - "integrity": "sha512-6kGubc7iBDWruEBUU7yR+sQ++SOhMuvKWvWeTZJKRZedthycdzYz7QVpua0FaZSAJm5/dIt8ymU4WQvxTtZgTQ==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@vuepress/shared-utils/-/shared-utils-1.8.3.tgz", + "integrity": "sha512-OQ3MY920UZrQ8/NXlOf7kovNiXUJ5OHDKBuP+mqTrADNmuYeHNM667d2EyRniG5UFsAgdTCpGaidu1GlaCX8kw==", "requires": { "chalk": "^2.3.2", "escape-html": "^1.0.3", @@ -14827,13 +15420,13 @@ } }, "@vuepress/theme-default": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@vuepress/theme-default/-/theme-default-1.8.2.tgz", - "integrity": "sha512-rE7M1rs3n2xp4a/GrweO8EGwqFn3EA5gnFWdVmVIHyr7C1nix+EqjpPQF1SVWNnIrDdQuCw38PqS+oND1K2vYw==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@vuepress/theme-default/-/theme-default-1.8.3.tgz", + "integrity": "sha512-thMZ2+og9HWq4+F0xaHfGJvrtakIsmF51br1oOrlrZTb4aJIvBP2v3B26+r/sF9gx0BpPYyuQZ4H3xUthMCeNA==", "requires": { - "@vuepress/plugin-active-header-links": "1.8.2", - "@vuepress/plugin-nprogress": "1.8.2", - "@vuepress/plugin-search": "1.8.2", + "@vuepress/plugin-active-header-links": "1.8.3", + "@vuepress/plugin-nprogress": "1.8.3", + "@vuepress/plugin-search": "1.8.3", "docsearch.js": "^2.5.2", "lodash": "^4.17.15", "stylus": "^0.54.8", @@ -15204,9 +15797,9 @@ "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", "requires": { "safer-buffer": "~2.1.0" } @@ -15305,15 +15898,15 @@ } }, "autoprefixer": { - "version": "9.8.6", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz", - "integrity": "sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg==", + "version": "9.8.8", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.8.tgz", + "integrity": "sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA==", "requires": { "browserslist": "^4.12.0", "caniuse-lite": "^1.0.30001109", - "colorette": "^1.2.1", "normalize-range": "^0.1.2", "num2fraction": "^1.2.2", + "picocolors": "^0.2.1", "postcss": "^7.0.32", "postcss-value-parser": "^4.1.0" } @@ -15337,9 +15930,9 @@ } }, "babel-loader": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.2.tgz", - "integrity": "sha512-JvTd0/D889PQBtUXJ2PXaKU/pjZDMtHA9V2ecm+eNRmmBCMR09a+fmpGTNwnJtFmFl5Ei7Vy47LjBb+L0wQ99g==", + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz", + "integrity": "sha512-n4Zeta8NC3QAsuyiizu0GkmRcQ6clkV9WFUnUf1iXP//IeSKbWjofW3UHyZVwlOB4y039YQKefawyTn64Zwbuw==", "requires": { "find-cache-dir": "^3.3.1", "loader-utils": "^1.4.0", @@ -15356,30 +15949,30 @@ } }, "babel-plugin-polyfill-corejs2": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.0.tgz", - "integrity": "sha512-9bNwiR0dS881c5SHnzCmmGlMkJLl0OUZvxrxHo9w/iNoRuqaPjqlvBf4HrovXtQs/au5yKkpcdgfT1cC5PAZwg==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.0.tgz", + "integrity": "sha512-wMDoBJ6uG4u4PNFh72Ty6t3EgfA91puCuAwKIazbQlci+ENb/UU9A3xG5lutjUIiXCIn1CY5L15r9LimiJyrSA==", "requires": { "@babel/compat-data": "^7.13.11", - "@babel/helper-define-polyfill-provider": "^0.2.0", + "@babel/helper-define-polyfill-provider": "^0.3.0", "semver": "^6.1.1" } }, "babel-plugin-polyfill-corejs3": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.0.tgz", - "integrity": "sha512-zZyi7p3BCUyzNxLx8KV61zTINkkV65zVkDAFNZmrTCRVhjo1jAS+YLvDJ9Jgd/w2tsAviCwFHReYfxO3Iql8Yg==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.4.0.tgz", + "integrity": "sha512-YxFreYwUfglYKdLUGvIF2nJEsGwj+RhWSX/ije3D2vQPOXuyMLMtg/cCGMDpOA7Nd+MwlNdnGODbd2EwUZPlsw==", "requires": { - "@babel/helper-define-polyfill-provider": "^0.2.0", - "core-js-compat": "^3.9.1" + "@babel/helper-define-polyfill-provider": "^0.3.0", + "core-js-compat": "^3.18.0" } }, "babel-plugin-polyfill-regenerator": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.0.tgz", - "integrity": "sha512-J7vKbCuD2Xi/eEHxquHN14bXAW9CXtecwuLrOIDJtcZzTaPzV1VdEfoUf9AzcRBMolKUQKM9/GVojeh0hFiqMg==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.0.tgz", + "integrity": "sha512-dhAPTDLGoMW5/84wkgwiLRwMnio2i1fUe53EuvtKMv0pn2p3S8OCoV1xAzfJPl0KOX7IB89s2ib85vbYiea3jg==", "requires": { - "@babel/helper-define-polyfill-provider": "^0.2.0" + "@babel/helper-define-polyfill-provider": "^0.3.0" } }, "babel-walk": { @@ -15467,31 +16060,31 @@ "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==" }, "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz", + "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==", "requires": { - "bytes": "3.1.0", + "bytes": "3.1.1", "content-type": "~1.0.4", "debug": "2.6.9", "depd": "~1.1.2", - "http-errors": "1.7.2", + "http-errors": "1.8.1", "iconv-lite": "0.4.24", "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" + "qs": "6.9.6", + "raw-body": "2.4.2", + "type-is": "~1.6.18" }, "dependencies": { "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", + "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==" }, "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", + "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==" } } }, @@ -15694,15 +16287,22 @@ } }, "browserslist": { - "version": "4.16.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.5.tgz", - "integrity": "sha512-C2HAjrM1AI/djrpAUU/tr4pml1DqLIzJKSLDBXBrNErl9ZCCTXdhwxdJjYc16953+mBWf7Lw+uUJgpgb8cN71A==", + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", + "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", "requires": { - "caniuse-lite": "^1.0.30001214", - "colorette": "^1.2.2", - "electron-to-chromium": "^1.3.719", + "caniuse-lite": "^1.0.30001286", + "electron-to-chromium": "^1.4.17", "escalade": "^3.1.1", - "node-releases": "^1.1.71" + "node-releases": "^2.0.1", + "picocolors": "^1.0.0" + }, + "dependencies": { + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + } } }, "buffer": { @@ -15716,9 +16316,9 @@ } }, "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, "buffer-indexof": { "version": "1.1.1", @@ -15972,9 +16572,9 @@ } }, "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==" + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.1.tgz", + "integrity": "sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA==" }, "caniuse-api": { "version": "3.0.0", @@ -15988,9 +16588,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001219", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001219.tgz", - "integrity": "sha512-c0yixVG4v9KBc/tQ2rlbB3A/bgBFRvl8h8M4IeUbqCca4gsiCfvtaheUssbnux/Mb66Vjz7x8yYjDgYcNQOhyQ==" + "version": "1.0.30001292", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001292.tgz", + "integrity": "sha512-jnT4Tq0Q4ma+6nncYQVe7d73kmDmE9C3OGTx3MvW7lBM/eY1S1DZTMBON7dqV481RhNiS5OxD7k9JQvmDOTirw==" }, "caseless": { "version": "0.12.0", @@ -16057,6 +16657,33 @@ "path-is-absolute": "^1.0.0", "readdirp": "^2.2.1", "upath": "^1.1.1" + }, + "dependencies": { + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } + } + } + } + }, + "chokidar2": { + "version": "file:node_modules/webpack/node_modules/watchpack/chokidar2", + "requires": { + "chokidar": "^2.1.8" } }, "chownr": { @@ -16156,9 +16783,9 @@ } }, "clean-css": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", - "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", + "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", "requires": { "source-map": "~0.6.0" }, @@ -16253,12 +16880,12 @@ } }, "color": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/color/-/color-3.1.3.tgz", - "integrity": "sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", "requires": { - "color-convert": "^1.9.1", - "color-string": "^1.5.4" + "color-convert": "^1.9.3", + "color-string": "^1.6.0" } }, "color-convert": { @@ -16275,19 +16902,14 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "color-string": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.5.tgz", - "integrity": "sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.0.tgz", + "integrity": "sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ==", "requires": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, - "colorette": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", - "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==" - }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -16400,11 +17022,18 @@ "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=" }, "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "5.2.1" + }, + "dependencies": { + "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==" + } } }, "content-type": { @@ -16413,17 +17042,17 @@ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", "requires": { "safe-buffer": "~5.1.1" } }, "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" }, "cookie-signature": { "version": "1.0.6", @@ -16495,6 +17124,25 @@ "locate-path": "^3.0.0" } }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, "globby": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", @@ -16582,16 +17230,16 @@ } }, "core-js": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.11.1.tgz", - "integrity": "sha512-k93Isqg7e4txZWMGNYwevZL9MiogLk8pd1PtwrmFmi8IBq4GXqUaVW/a33Llt6amSI36uSjd0GWwc9pTT9ALlQ==" + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.20.0.tgz", + "integrity": "sha512-KjbKU7UEfg4YPpskMtMXPhUKn7m/1OdTHTVjy09ScR2LVaoUXe8Jh0UdvN2EKUR6iKTJph52SJP95mAB0MnVLQ==" }, "core-js-compat": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.11.1.tgz", - "integrity": "sha512-aZ0e4tmlG/aOBHj92/TuOuZwp6jFvn1WNabU5VOVixzhu5t5Ao+JZkQOPlgNXu6ynwLrwJxklT4Gw1G1VGEh+g==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.20.0.tgz", + "integrity": "sha512-relrah5h+sslXssTTOkvqcC/6RURifB0W5yhYBdBkaPYa5/2KBMiog3XiD+s3TwEHWxInWVv4Jx2/Lw0vng+IQ==", "requires": { - "browserslist": "^4.16.5", + "browserslist": "^4.19.1", "semver": "7.0.0" }, "dependencies": { @@ -16603,9 +17251,9 @@ } }, "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "cosmiconfig": { "version": "5.2.1", @@ -17082,9 +17730,9 @@ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, "detect-node": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.5.tgz", - "integrity": "sha512-qi86tE6hRcFHy8jI1m2VG+LaPUR1LhqDa5G8tVjuUXmOrpuAgqsA1pN0+ldgr3aKUH+QLI9hCY/OcRYisERejw==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" }, "diffie-hellman": { "version": "5.0.3", @@ -17281,9 +17929,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "electron-to-chromium": { - "version": "1.3.723", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.723.tgz", - "integrity": "sha512-L+WXyXI7c7+G1V8ANzRsPI5giiimLAUDC6Zs1ojHHPhYXb3k/iTABFmWjivEtsWrRQymjnO66/rO2ZTABGdmWg==" + "version": "1.4.26", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.26.tgz", + "integrity": "sha512-cA1YwlRzO6TGp7yd3+KAqh9Tt6Z4CuuKqsAJP6uF/H5MQryjAGDhMhnY5cEXo8MaRCczpzSBhMPdqRIodkbZYw==" }, "elliptic": { "version": "6.5.4", @@ -17386,26 +18034,30 @@ } }, "es-abstract": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz", - "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", + "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", "requires": { "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", "has": "^1.0.3", "has-symbols": "^1.0.2", - "is-callable": "^1.2.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.2", - "is-string": "^1.0.5", - "object-inspect": "^1.9.0", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.1", + "is-string": "^1.0.7", + "is-weakref": "^1.0.1", + "object-inspect": "^1.11.0", "object-keys": "^1.1.1", "object.assign": "^4.1.2", "string.prototype.trimend": "^1.0.4", "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.0" + "unbox-primitive": "^1.0.1" } }, "es-to-primitive": { @@ -17471,9 +18123,9 @@ }, "dependencies": { "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==" + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" } } }, @@ -17609,16 +18261,16 @@ } }, "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz", + "integrity": "sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==", "requires": { "accepts": "~1.3.7", "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", + "body-parser": "1.19.1", + "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.0", + "cookie": "0.4.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "~1.1.2", @@ -17632,13 +18284,13 @@ "on-finished": "~2.3.0", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", + "proxy-addr": "~2.0.7", + "qs": "6.9.6", "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", + "safe-buffer": "5.2.1", + "send": "0.17.2", + "serve-static": "1.14.2", + "setprototypeof": "1.2.0", "statuses": "~1.5.0", "type-is": "~1.6.18", "utils-merge": "1.0.1", @@ -17651,9 +18303,14 @@ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", + "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==" + }, + "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==" } } }, @@ -17716,6 +18373,27 @@ "is-glob": "^4.0.0", "merge2": "^1.2.3", "micromatch": "^3.1.10" + }, + "dependencies": { + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } + } + } } }, "fast-json-stable-stringify": { @@ -17724,9 +18402,9 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "faye-websocket": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", - "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", "requires": { "websocket-driver": ">=0.5.1" } @@ -17797,9 +18475,9 @@ } }, "find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", "requires": { "commondir": "^1.0.1", "make-dir": "^3.0.2", @@ -17855,9 +18533,9 @@ } }, "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" }, "fragment-cache": { "version": "0.2.1", @@ -17955,6 +18633,15 @@ "pump": "^3.0.0" } }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", @@ -17982,22 +18669,11 @@ } }, "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "requires": { - "is-extglob": "^2.1.0" - } - } + "is-glob": "^4.0.1" } }, "glob-to-regexp": { @@ -18133,6 +18809,14 @@ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "requires": { + "has-symbols": "^1.0.2" + } + }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -18323,28 +19007,21 @@ "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=" }, "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "requires": { "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - } + "toidentifier": "1.0.1" } }, "http-parser-js": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", - "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==" + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.5.tgz", + "integrity": "sha512-x+JVEkO2PoM8qqpbPbOL3cqHPwerep7OwzK7Ay+sMQjKzaKCqWvjoXm5tqMP9tXWWTnTzAjIhXg+J99XYuPhPA==" }, "http-proxy": { "version": "1.18.1", @@ -18545,6 +19222,16 @@ "ipaddr.js": "^1.9.0" } }, + "internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "requires": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, "ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", @@ -18574,11 +19261,12 @@ } }, "is-arguments": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz", - "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", "requires": { - "call-bind": "^1.0.0" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" } }, "is-arrayish": { @@ -18587,9 +19275,12 @@ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "is-bigint": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.1.tgz", - "integrity": "sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "requires": { + "has-bigints": "^1.0.1" + } }, "is-binary-path": { "version": "1.0.1", @@ -18600,11 +19291,12 @@ } }, "is-boolean-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz", - "integrity": "sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", "requires": { - "call-bind": "^1.0.0" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" } }, "is-buffer": { @@ -18613,9 +19305,9 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "is-callable": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", - "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==" + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==" }, "is-ci": { "version": "2.0.0", @@ -18655,9 +19347,12 @@ } }, "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "requires": { + "has-tostringtag": "^1.0.0" + } }, "is-descriptor": { "version": "1.0.2", @@ -18699,9 +19394,9 @@ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "requires": { "is-extglob": "^2.1.1" } @@ -18716,9 +19411,9 @@ } }, "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==" }, "is-npm": { "version": "4.0.0", @@ -18744,9 +19439,12 @@ } }, "is-number-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", - "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", + "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "requires": { + "has-tostringtag": "^1.0.0" + } }, "is-obj": { "version": "2.0.0", @@ -18800,12 +19498,12 @@ "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" }, "is-regex": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", - "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "requires": { "call-bind": "^1.0.2", - "has-symbols": "^1.0.1" + "has-tostringtag": "^1.0.0" } }, "is-resolvable": { @@ -18813,22 +19511,30 @@ "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==" }, + "is-shared-array-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", + "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==" + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "requires": { + "has-tostringtag": "^1.0.0" + } }, "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", "requires": { - "has-symbols": "^1.0.1" + "has-symbols": "^1.0.2" } }, "is-typedarray": { @@ -18836,6 +19542,14 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "requires": { + "call-bind": "^1.0.2" + } + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -19123,9 +19837,9 @@ "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" }, "loglevel": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz", - "integrity": "sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz", + "integrity": "sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA==" }, "lower-case": { "version": "1.1.4", @@ -19356,21 +20070,21 @@ } }, "mime": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", - "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==" + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==" }, "mime-db": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", - "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==" + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" }, "mime-types": { - "version": "2.1.30", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", - "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", + "version": "2.1.34", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", + "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", "requires": { - "mime-db": "1.47.0" + "mime-db": "1.51.0" } }, "mimic-response": { @@ -19516,9 +20230,9 @@ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=" }, "nan": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", - "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", "optional": true }, "nanomatch": { @@ -19624,9 +20338,9 @@ } }, "node-releases": { - "version": "1.1.71", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz", - "integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", + "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==" }, "nopt": { "version": "1.0.10", @@ -19754,9 +20468,9 @@ } }, "object-inspect": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.2.tgz", - "integrity": "sha512-gz58rdPpadwztRrPjZE9DZLOABUpTGdcANUgOwBFO1C+HZZhePoP83M65WGDmbpwFYJSWqavbl4SgDn4k8RYTA==" + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==" }, "object-is": { "version": "1.1.5", @@ -19792,13 +20506,13 @@ } }, "object.getownpropertydescriptors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz", - "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.3.tgz", + "integrity": "sha512-VdDoCwvJI4QdC6ndjpqFmoL3/+HxffFBbcJzKi5hwLLqqx3mdbedRpfZDdK0SrOSauj8X4GzBvnDZl4vTN7dOw==", "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2" + "es-abstract": "^1.19.1" } }, "object.pick": { @@ -19810,14 +20524,13 @@ } }, "object.values": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.3.tgz", - "integrity": "sha512-nkF6PfDB9alkOUxpf1HNm/QlkeW3SReqL5WXeBLpEJJnlPSvRaDQpW3gQTksTN3fgJX4hL42RzKyOin6ff3tyw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2", - "has": "^1.0.3" + "es-abstract": "^1.19.1" } }, "obuf": { @@ -20644,9 +21357,9 @@ } }, "postcss-selector-parser": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.5.tgz", - "integrity": "sha512-aFYPoYmXbZ1V6HZaSvat08M97A8HqO6Pjz+PiNpw/DhuRrC72XWAdp3hL6wusDCN31sSmcZyMGa2hZEuX+Xfhg==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.8.tgz", + "integrity": "sha512-D5PG53d209Z1Uhcc0qAZ5U3t5HagH3cxu+WLZ22jt3gLUpXM4eXXfiO14jiDWST3NNooX/E8wISfOhZ9eIjGTQ==", "requires": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -20680,9 +21393,9 @@ } }, "postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "prepend-http": { "version": "2.0.0", @@ -20690,9 +21403,9 @@ "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" }, "prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", "optional": true }, "pretty-error": { @@ -20738,11 +21451,11 @@ "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" }, "proxy-addr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", - "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "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==", "requires": { - "forwarded": "~0.1.2", + "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, @@ -21002,20 +21715,20 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", + "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==", "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", + "bytes": "3.1.1", + "http-errors": "1.8.1", "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, "dependencies": { "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", + "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==" } } }, @@ -21068,17 +21781,17 @@ "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" }, "regenerate-unicode-properties": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", - "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz", + "integrity": "sha512-3E12UeNSPfjrgwjkR81m5J7Aw/T55Tu7nUyZVQYCKEOs+2dkxEY+DpPtZzO4YruuiPb7NkYLVcyJC4+zCbk5pA==", "requires": { - "regenerate": "^1.4.0" + "regenerate": "^1.4.2" } }, "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" }, "regenerator-transform": { "version": "0.14.5", @@ -21126,16 +21839,16 @@ } }, "regexpu-core": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", - "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.8.0.tgz", + "integrity": "sha512-1F6bYsoYiz6is+oz70NWur2Vlh9KWtswuRuzJOfeYUrfPX2o8n74AnUVaOGDbUqVGO9fNHu48/pjJO4sNVwsOg==", "requires": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.2.0", - "regjsgen": "^0.5.1", - "regjsparser": "^0.6.4", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.2.0" + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^9.0.0", + "regjsgen": "^0.5.2", + "regjsparser": "^0.7.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.0.0" } }, "registry-auth-token": { @@ -21160,9 +21873,9 @@ "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==" }, "regjsparser": { - "version": "0.6.9", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.9.tgz", - "integrity": "sha512-ZqbNRz1SNjLAiYuwY0zoXW8Ne675IX5q+YHioAGbCw4X96Mjl2+dcX9B2ciaeyYjViDAfvIjFpQjJgLttTEERQ==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.7.0.tgz", + "integrity": "sha512-A4pcaORqmNMDVwUjWoTzuhwMGpP+NykpfqAsEgI1FSH/EzC7lrN5TMd+kN8YCovX+jMpu8eaqXgXPCa0g8FQNQ==", "requires": { "jsesc": "~0.5.0" }, @@ -21391,9 +22104,9 @@ "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=" }, "selfsigned": { - "version": "1.10.8", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz", - "integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==", + "version": "1.10.11", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.11.tgz", + "integrity": "sha512-aVmbPOfViZqOZPgRBT0+3u4yZFHpmnIghLMlAcb5/xhp5ZtB/RVnKhz5vl2M32CLXAqR4kha9zfhNg0Lf/sxKA==", "requires": { "node-forge": "^0.10.0" } @@ -21412,9 +22125,9 @@ } }, "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", + "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", "requires": { "debug": "2.6.9", "depd": "~1.1.2", @@ -21423,9 +22136,9 @@ "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "~1.7.2", + "http-errors": "1.8.1", "mime": "1.6.0", - "ms": "2.1.1", + "ms": "2.1.3", "on-finished": "~2.3.0", "range-parser": "~1.2.1", "statuses": "~1.5.0" @@ -21437,9 +22150,9 @@ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" } } }, @@ -21489,14 +22202,14 @@ } }, "serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", + "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.1" + "send": "0.17.2" } }, "set-blocking": { @@ -21521,9 +22234,9 @@ "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" }, "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "sha.js": { "version": "2.4.11", @@ -21547,6 +22260,16 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", @@ -21708,26 +22431,33 @@ } }, "sockjs": { - "version": "0.3.21", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.21.tgz", - "integrity": "sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw==", + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", "requires": { "faye-websocket": "^0.11.3", - "uuid": "^3.4.0", + "uuid": "^8.3.2", "websocket-driver": "^0.7.4" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + } } }, "sockjs-client": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.5.1.tgz", - "integrity": "sha512-VnVAb663fosipI/m6pqRXakEOw7nvd7TUgdr3PlR/8V2I95QIdwT8L4nMxhyU8SmDBHYXU1TOElaKOmKLfYzeQ==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.5.2.tgz", + "integrity": "sha512-ZzRxPBISQE7RpzlH4tKJMQbHM9pabHluk0WBaxAQ+wm/UieeBVBou0p4wVnSQGN9QmpAZygQ0cDIypWuqOFmFQ==", "requires": { "debug": "^3.2.6", "eventsource": "^1.0.7", "faye-websocket": "^0.11.3", "inherits": "^2.0.4", "json3": "^3.3.3", - "url-parse": "^1.5.1" + "url-parse": "^1.5.3" }, "dependencies": { "debug": { @@ -21776,9 +22506,9 @@ } }, "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "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==", "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -21809,9 +22539,9 @@ }, "dependencies": { "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "requires": { "ms": "2.1.2" } @@ -21837,9 +22567,9 @@ }, "dependencies": { "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "requires": { "ms": "2.1.2" } @@ -22013,17 +22743,17 @@ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, "std-env": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-2.3.0.tgz", - "integrity": "sha512-4qT5B45+Kjef2Z6pE0BkskzsH0GO7GrND0wGlTM1ioUe3v0dGYx9ZJH0Aro/YyA8fqQ5EyIKDRjZojJYMFTflw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-2.3.1.tgz", + "integrity": "sha512-eOsoKTWnr6C8aWrqJJ2KAReXoa7Vn5Ywyw6uCXgA/xDhxPoaIsBa5aNJmISY04dLwXPBnDHW4diGM7Sn5K4R/g==", "requires": { - "ci-info": "^3.0.0" + "ci-info": "^3.1.1" }, "dependencies": { "ci-info": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.1.1.tgz", - "integrity": "sha512-kdRWLBIJwdsYJWYJFtAFFYxybguqeF91qpZaggjG5Nf8QKdizFG2hjqvaTXbxFIcYbSaD74KpAXv6BSm17DHEQ==" + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz", + "integrity": "sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==" } } }, @@ -22517,9 +23247,9 @@ } }, "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, "token-stream": { "version": "1.0.0", @@ -22636,28 +23366,28 @@ } }, "unicode-canonical-property-names-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", - "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==" + "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==" }, "unicode-match-property-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", - "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "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==", "requires": { - "unicode-canonical-property-names-ecmascript": "^1.0.4", - "unicode-property-aliases-ecmascript": "^1.0.4" + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" } }, "unicode-match-property-value-ecmascript": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", - "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", + "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==" }, "unicode-property-aliases-ecmascript": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", - "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", + "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==" }, "union-value": { "version": "1.0.1", @@ -22977,6 +23707,13 @@ "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + } } }, "vm-browserify": { @@ -22990,9 +23727,9 @@ "integrity": "sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=" }, "vue": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.12.tgz", - "integrity": "sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg==" + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz", + "integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==" }, "vue-hot-reload-api": { "version": "2.3.4", @@ -23000,9 +23737,9 @@ "integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==" }, "vue-loader": { - "version": "15.9.6", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.9.6.tgz", - "integrity": "sha512-j0cqiLzwbeImIC6nVIby2o/ABAWhlppyL/m5oJ67R5MloP0hj/DtFgb0Zmq3J9CG7AJ+AXIvHVnJAPBvrLyuDg==", + "version": "15.9.8", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.9.8.tgz", + "integrity": "sha512-GwSkxPrihfLR69/dSV3+5CdMQ0D+jXg8Ma1S4nQXKJAznYFX14vHdc/NetQc34Dw+rBbIJyP7JOuVb9Fhprvog==", "requires": { "@vue/component-compiler-utils": "^3.1.0", "hash-sum": "^1.0.2", @@ -23012,14 +23749,14 @@ } }, "vue-router": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.5.1.tgz", - "integrity": "sha512-RRQNLT8Mzr8z7eL4p7BtKvRaTSGdCbTy2+Mm5HTJvLGYSSeG9gDzNasJPP/yOYKLy+/cLG/ftrqq5fvkFwBJEw==" + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.5.3.tgz", + "integrity": "sha512-FUlILrW3DGitS2h+Xaw8aRNvGTwtuaxrRkNSHWTizOfLUie7wuYwezeZ50iflRn8YPV5kxmU2LQuu3nM/b3Zsg==" }, "vue-server-renderer": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/vue-server-renderer/-/vue-server-renderer-2.6.12.tgz", - "integrity": "sha512-3LODaOsnQx7iMFTBLjki8xSyOxhCtbZ+nQie0wWY4iOVeEtTg1a3YQAjd82WvKxrWHHTshjvLb7OXMc2/dYuxw==", + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vue-server-renderer/-/vue-server-renderer-2.6.14.tgz", + "integrity": "sha512-HifYRa/LW7cKywg9gd4ZtvtRuBlstQBao5ZCWlg40fyB4OPoGfEXAzxb0emSLv4pBDOHYx0UjpqvxpiQFEuoLA==", "requires": { "chalk": "^1.1.3", "hash-sum": "^1.0.2", @@ -23091,9 +23828,9 @@ } }, "vue-template-compiler": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.12.tgz", - "integrity": "sha512-OzzZ52zS41YUbkCBfdXShQTe69j1gQDZ9HIX8miuC9C3rBCk9wIRjLiZZLrmX9V+Ftq/YEyv1JaVr5Y/hNtByg==", + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz", + "integrity": "sha512-ODQS1SyMbjKoO1JBJZojSw6FE4qnh9rIpUZn2EUT86FKizx9uH5z6uXiIrm4/Nb/gwxTi/o17ZDEGWAXHvtC7g==", "requires": { "de-indent": "^1.0.2", "he": "^1.1.0" @@ -23105,12 +23842,12 @@ "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==" }, "vuepress": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/vuepress/-/vuepress-1.8.2.tgz", - "integrity": "sha512-BU1lUDwsA3ghf7a9ga4dsf0iTc++Z/l7BR1kUagHWVBHw7HNRgRDfAZBDDQXhllMILVToIxaTifpne9mSi94OA==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/vuepress/-/vuepress-1.8.3.tgz", + "integrity": "sha512-1iro+COj3lAXiIS0B3HApYzFZxE6GHmXjxoYrZJvFds3W3gcynxw/umM5hxA+goyx8n/OtsuHN60/qstz9gDVQ==", "requires": { - "@vuepress/core": "1.8.2", - "@vuepress/theme-default": "1.8.2", + "@vuepress/core": "1.8.3", + "@vuepress/theme-default": "1.8.3", "cac": "^6.5.6", "envinfo": "^7.2.0", "opencollective-postinstall": "^2.0.2", @@ -23169,11 +23906,10 @@ } }, "vuepress-plugin-container": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/vuepress-plugin-container/-/vuepress-plugin-container-2.1.5.tgz", - "integrity": "sha512-TQrDX/v+WHOihj3jpilVnjXu9RcTm6m8tzljNJwYhxnJUW0WWQ0hFLcDTqTBwgKIFdEiSxVOmYE+bJX/sq46MA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/vuepress-plugin-container/-/vuepress-plugin-container-2.1.4.tgz", + "integrity": "sha512-l+EkeL+rC6DJch1wAZUFIkNDaz2TNOg4NQTHa3yMAsYkC+QaSRubGdN6YwOSmfjxVmM9s9D3gwBWw0O7OBhqRg==", "requires": { - "@vuepress/shared-utils": "^1.2.0", "markdown-it-container": "^2.0.0" } }, @@ -23246,15 +23982,6 @@ } } }, - "watchpack-chokidar2": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", - "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", - "optional": true, - "requires": { - "chokidar": "^2.1.8" - } - }, "wbuf": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", @@ -23269,9 +23996,9 @@ "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" }, "webpack": { - "version": "4.46.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz", - "integrity": "sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.43.0.tgz", + "integrity": "sha512-GW1LjnPipFW2Y78OOab8NJlCflB7EFskMih2AHdvjbpKMeDJqEgSx24cXXXiPS65+WSwVyxtDsJH6jGX2czy+g==", "requires": { "@webassemblyjs/ast": "1.9.0", "@webassemblyjs/helper-module-context": "1.9.0", @@ -23281,7 +24008,7 @@ "ajv": "^6.10.2", "ajv-keywords": "^3.4.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^4.5.0", + "enhanced-resolve": "^4.1.0", "eslint-scope": "^4.0.3", "json-parse-better-errors": "^1.0.2", "loader-runner": "^2.4.0", @@ -23294,7 +24021,7 @@ "schema-utils": "^1.0.0", "tapable": "^1.1.3", "terser-webpack-plugin": "^1.4.3", - "watchpack": "^1.7.4", + "watchpack": "^1.6.1", "webpack-sources": "^1.4.1" }, "dependencies": { @@ -23359,15 +24086,6 @@ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "optional": true }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "optional": true, - "requires": { - "is-glob": "^4.0.1" - } - }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -23420,14 +24138,14 @@ } }, "watchpack": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", - "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.1.tgz", + "integrity": "sha512-1OeW6LucExk7h6lBuCr1isK5261Tf0PHNRG9tZjg2WKUsSkPwvyv37d7mgAUk1rZjxxaL/6WttSGMUY2hn/20g==", "requires": { - "chokidar": "^3.4.1", + "chokidar": "^3.4.0", + "chokidar2": "file:chokidar2", "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0", - "watchpack-chokidar2": "^2.0.1" + "neo-async": "^2.5.0" } } } @@ -23509,9 +24227,9 @@ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "requires": { "ms": "2.1.2" } diff --git a/docs/package.json b/docs/package.json index e97a0bad3ed..66d4978ec4e 100644 --- a/docs/package.json +++ b/docs/package.json @@ -13,6 +13,8 @@ "author": "", "license": "ISC", "dependencies": { + "glob-parent": "^5.1.2", + "vue": "^2.6.14", "vuepress-theme-cosmos": "^1.0.182" }, "devDependencies": { diff --git a/docs/roadmap/roadmap.md b/docs/roadmap/roadmap.md index ea0e83e8b01..5bc00fe8ee9 100644 --- a/docs/roadmap/roadmap.md +++ b/docs/roadmap/roadmap.md @@ -4,13 +4,21 @@ order: 1 # Roadmap ibc-go -_Lastest update: Dec 16, 2021_ +_Lastest update: Dec 22, 2021_ This document endeavours to inform the wider IBC community about plans and priorities for work on ibc-go byt 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. -The release tags and timelines are educated guesses based on the information at hand at the moment of updating this document. The `x` in the release tags is a placeholder for the final version number. +The release tags and timelines are educated guesses based on the information at hand at the moment of updating this document. Since predicting the final version number (specially for minor and patch numbers) can be challenging (since we might need to release unforeseen security vulnerability patches or urgent bug fixes), we are using alphabet letters as placeholders. Once we get closer to the release date, the placeholder will be replaced with the right number. An example for clarification... + +Let's assume that the planned release schedule looks like the following: +- At time `t0`: + - The first patch release for the `v2.0.x` release series with release tag `v2.0.a`. The placeholder is `a` since this is the first patch release. + - The first minor release for the `v2.x` release series with release tag `v2.a.0`. The placeholder is `a` since this is the first minor release. +- At time `t0 + delta`: + - The second patch release for the `v2.0.x` release series with release tag `v2.0.b`. The placehoder is `b` since this is the next patch release of this release series after `v2.0.a`. + - The first patch release for the new `v2.a.x` release series with release tag `v2.a.a`. The patch version placeholder is `a` because this is the first patch release of the series. ## Q4 - 2021 @@ -36,24 +44,21 @@ We will work to bring the ibc-go implementation in line with [ICS02](https://git |Release|Milestone|Date| |-------|---------|----| -|[v1.1.0](https://github.com/cosmos/ibc-go/releases/tag/v1.1.1)||Oct 04, 2021| -|[v1.2.1](https://github.com/cosmos/ibc-go/releases/tag/v1.2.1)||Oct 04, 2021| -|[v2.0.0-rc0](https://github.com/cosmos/ibc-go/releases/tag/v2.0.0-rc0)|[Link](https://github.com/cosmos/ibc-go/milestone/3)|Oct 05, 2021| -|[v1.1.2](https://github.com/cosmos/ibc-go/releases/tag/v1.1.2)||Oct 15, 2021| -|[v1.2.2](https://github.com/cosmos/ibc-go/releases/tag/v1.2.2)||Oct 15, 2021| -|[v1.1.3](https://github.com/cosmos/ibc-go/releases/tag/v1.1.3)||Nov 09, 2021| -|[v1.2.3](https://github.com/cosmos/ibc-go/releases/tag/v1.2.3)||Nov 09, 2021| -|[v2.0.0](https://github.com/cosmos/ibc-go/releases/tag/v2.0.0)|[Link](https://github.com/cosmos/ibc-go/milestone/3)|Nov 09, 2021| -|[v1.1.4](https://github.com/cosmos/ibc-go/releases/tag/v1.1.5)||Dec 06, 2021| -|[v1.2.4](https://github.com/cosmos/ibc-go/releases/tag/v1.2.4)||Dec 06, 2021| -|[v2.0.1](https://github.com/cosmos/ibc-go/releases/tag/v2.0.1)|[Link](https://github.com/cosmos/ibc-go/milestone/11)|Dec 06, 2021| -|[v1.1.5](https://github.com/cosmos/ibc-go/releases/tag/v1.1.5)||Dec 15, 2021| -|[v1.2.5](https://github.com/cosmos/ibc-go/releases/tag/v1.2.5)||Dec 15, 2021| -|[v2.0.2](https://github.com/cosmos/ibc-go/releases/tag/v2.0.2)|[Link](https://github.com/cosmos/ibc-go/milestone/20)|Dec 15, 2021| - -#### H1 December - -- v3.0.0-alpha: Alpha release of v3.0.0 including Interchain Accounts, an update of Golang from v1.15 to v1.17, and some core improvements. See [v3.0.0 milestone](https://github.com/cosmos/ibc-go/milestone/12) for more details. +|[`v1.1.0`](https://github.com/cosmos/ibc-go/releases/tag/v1.1.1)||Oct 04, 2021| +|[`v1.2.1`](https://github.com/cosmos/ibc-go/releases/tag/v1.2.1)||Oct 04, 2021| +|[`v2.0.0-rc0`](https://github.com/cosmos/ibc-go/releases/tag/v2.0.0-rc0)|[Link](https://github.com/cosmos/ibc-go/milestone/3)|Oct 05, 2021| +|[`v1.1.2`](https://github.com/cosmos/ibc-go/releases/tag/v1.1.2)||Oct 15, 2021| +|[`v1.2.2`](https://github.com/cosmos/ibc-go/releases/tag/v1.2.2)||Oct 15, 2021| +|[`v1.1.3`](https://github.com/cosmos/ibc-go/releases/tag/v1.1.3)||Nov 09, 2021| +|[`v1.2.3`](https://github.com/cosmos/ibc-go/releases/tag/v1.2.3)||Nov 09, 2021| +|[`v2.0.0`](https://github.com/cosmos/ibc-go/releases/tag/v2.0.0)|[Link](https://github.com/cosmos/ibc-go/milestone/3)|Nov 09, 2021| +|[`v1.1.4`](https://github.com/cosmos/ibc-go/releases/tag/v1.1.5)||Dec 06, 2021| +|[`v1.2.4`](https://github.com/cosmos/ibc-go/releases/tag/v1.2.4)||Dec 06, 2021| +|[`v2.0.1`](https://github.com/cosmos/ibc-go/releases/tag/v2.0.1)|[Link](https://github.com/cosmos/ibc-go/milestone/11)|Dec 06, 2021| +|[`v1.1.5`](https://github.com/cosmos/ibc-go/releases/tag/v1.1.5)||Dec 15, 2021| +|[`v1.2.5`](https://github.com/cosmos/ibc-go/releases/tag/v1.2.5)||Dec 15, 2021| +|[`v2.0.2`](https://github.com/cosmos/ibc-go/releases/tag/v2.0.2)|[Link](https://github.com/cosmos/ibc-go/milestone/20)|Dec 15, 2021| +|[`v3.0.0-alpha1`](https://github.com/cosmos/ibc-go/releases/tag/v3.0.0-alpha1)|[Link](https://github.com/cosmos/ibc-go/milestone/12)|Dec 21, 2021| ## Q1 - 2022 @@ -90,33 +95,33 @@ There is an open [PR](https://github.com/cosmos/ibc-go/pull/208) that implements #### H1 January -- [v3.0.0-beta](https://github.com/cosmos/ibc-go/milestone/12): Beta release of v3.0.0 including Interchain Accounts, an update of Golang from v1.15 to v1.17, and some core improvements. This is a Go-API breaking change because of [#472](https://github.com/cosmos/ibc-go/issues/472). +- [`v3.0.0-beta`](https://github.com/cosmos/ibc-go/milestone/12): Beta release of `v3.0.0` including Interchain Accounts, an update of Golang from `v1.15` to `v1.17`, and some core improvements. This is a Go-API breaking change because of [#472](https://github.com/cosmos/ibc-go/issues/472). #### H2 January -- [v2.0.x](https://github.com/cosmos/ibc-go/milestone/14) -- [v3.0.0-rc0](https://github.com/cosmos/ibc-go/milestone/12): Release candidate 0 of v3.0.0 including Interchain Accounts, an update of Golang from v1.15 to v1.17, and some core improvements. This is a Go-API breaking change because of [#472](https://github.com/cosmos/ibc-go/issues/472). -- [v4.0.0-alpha](https://github.com/cosmos/ibc-go/milestone/16): Alpha release of v4.0.0 including Relayer Incentivisation and the issues to bring ibc-go implementation in line with ICS02 (which are Go-API breaking changes). This release will include fixes to issues that surfaced during internal audit. +- [`v2.0.a`](https://github.com/cosmos/ibc-go/milestone/14) +- [`v3.0.0-rc0`](https://github.com/cosmos/ibc-go/milestone/12): Release candidate 0 of `v3.0.0` including Interchain Accounts, an update of Golang from `v1.15` to `v1.17`, and some core improvements. This is a Go-API breaking change because of [#472](https://github.com/cosmos/ibc-go/issues/472). +- [`v4.0.0-alpha`](https://github.com/cosmos/ibc-go/milestone/16): Alpha release of `v4.0.0` including Relayer Incentivisation and the issues to bring ibc-go implementation in line with ICS02 (which are Go-API breaking changes). This release will include fixes to issues that surfaced during internal audit. #### H1 February -- [v3.0.0](https://github.com/cosmos/ibc-go/milestone/12): Final release of v3.0.0 including Interchain Accounts, an update of Golang from v1.15 to v1.17, and some core improvements. This is a Go-API breaking change because of [#472](https://github.com/cosmos/ibc-go/issues/472). +- [`v3.0.0`](https://github.com/cosmos/ibc-go/milestone/12): Final release of `v3.0.0` including Interchain Accounts, an update of Golang from `v1.15` to `v1.17`, and some core improvements. This is a Go-API breaking change because of [#472](https://github.com/cosmos/ibc-go/issues/472). #### H2 February -- [v4.0.0-beta](https://github.com/cosmos/ibc-go/milestone/16): Beta release of v4.0.0 including Relayer Incentivisation and the issues to bring ibc-go implementation in line with ICS02 (which are Go-API breaking changes). This release will include fixes to issues that surfaced during external audit. +- [`v4.0.0-beta`](https://github.com/cosmos/ibc-go/milestone/16): Beta release of `v4.0.0` including Relayer Incentivisation and the issues to bring ibc-go implementation in line with ICS02 (which are Go-API breaking changes). This release will include fixes to issues that surfaced during external audit. #### H1 March -- [v4.0.0-rc0](https://github.com/cosmos/ibc-go/milestone/16): Release candidate 0 of v4.0.0 including Relayer Incentivisation and the issues to bring ibc-go implementation in line with ICS02 (which are Go-API breaking changes). +- [`v4.0.0-rc0`](https://github.com/cosmos/ibc-go/milestone/16): Release candidate 0 of `v4.0.0` including Relayer Incentivisation and the issues to bring ibc-go implementation in line with ICS02 (which are Go-API breaking changes). #### H2 March -- [v4.0.0](https://github.com/cosmos/ibc-go/milestone/16): Final release of v4.0.0 including Relayer Incentivisation and the issues to bring ibc-go implementation in line with ICS02 (which are Go-API breaking changes). -- [v1.x.0](https://github.com/cosmos/ibc-go/milestone/17): Release in v1.x line including the update of Cosmos SDK to [v0.45](https://github.com/cosmos/cosmos-sdk/milestone/46) and Tendermint to [v0.35](https://github.com/tendermint/tendermint/releases/tag/v0.35.0). -- [v2.x.0](https://github.com/cosmos/ibc-go/milestone/18): Release in v2.x line including the update of Cosmos SDK to [v0.45](https://github.com/cosmos/cosmos-sdk/milestone/46) and Tendermint to [v0.35](https://github.com/tendermint/tendermint/releases/tag/v0.35.0). -- [v3.x.0](https://github.com/cosmos/ibc-go/milestone/19): Release in v3.x line including the update of Cosmos SDK to [v0.45](https://github.com/cosmos/cosmos-sdk/milestone/46) and Tendermint to [v0.35](https://github.com/tendermint/tendermint/releases/tag/v0.35.0). -- [v4.x.0](https://github.com/cosmos/ibc-go/milestone/22): Release in v4.x line including the update of Cosmos SDK to [v0.45](https://github.com/cosmos/cosmos-sdk/milestone/46) and Tendermint to [v0.35](https://github.com/tendermint/tendermint/releases/tag/v0.35.0). +- [`v4.0.0`](https://github.com/cosmos/ibc-go/milestone/16): Final release of `v4.0.0` including Relayer Incentivisation and the issues to bring ibc-go implementation in line with ICS02 (which are Go-API breaking changes). +- [`v1.a.0`](https://github.com/cosmos/ibc-go/milestone/17): Minor release in `v1.x` seires including the update of Cosmos SDK to [`v0.45`](https://github.com/cosmos/cosmos-sdk/milestone/46) and Tendermint to [`v0.35`](https://github.com/tendermint/tendermint/releases/tag/v0.35.0). +- [`v2.a.0`](https://github.com/cosmos/ibc-go/milestone/18): Minor release in `v2.x` series including the update of Cosmos SDK to [`v0.45`](https://github.com/cosmos/cosmos-sdk/milestone/46) and Tendermint to [`v0.35`](https://github.com/tendermint/tendermint/releases/tag/v0.35.0). +- [`v3.a.0`](https://github.com/cosmos/ibc-go/milestone/19): Minor release in `v3.x` series including the update of Cosmos SDK to [v0.45](https://github.com/cosmos/cosmos-sdk/milestone/46) and Tendermint to [`v0.35`](https://github.com/tendermint/tendermint/releases/tag/v0.35.0). +- [`v4.a.0`](https://github.com/cosmos/ibc-go/milestone/22): Minor release in `v4.x` series including the update of Cosmos SDK to [`v0.45`](https://github.com/cosmos/cosmos-sdk/milestone/46) and Tendermint to [`v0.35`](https://github.com/tendermint/tendermint/releases/tag/v0.35.0). ## Q2 - 2022 @@ -126,8 +131,8 @@ Scope is still TBD. #### H1 April -- [v5.0.0-rc0](https://github.com/cosmos/ibc-go/milestone/21): Release candidate that includes the update of Cosmos SDK from 0.45 to [v1.0](https://github.com/cosmos/cosmos-sdk/milestone/52) and that will support the migration to SMT storage. +- [`v5.0.0-rc0`](https://github.com/cosmos/ibc-go/milestone/21): Release candidate that includes the update of Cosmos SDK from `v0.45` to [`v1.0`](https://github.com/cosmos/cosmos-sdk/milestone/52) and that will support the migration to SMT storage. #### H2 April -- [v5.0.0](https://github.com/cosmos/ibc-go/milestone/21): Final release that includes the update of Cosmos SDK from 0.45 to [v1.0](https://github.com/cosmos/cosmos-sdk/milestone/52) and that will support the migration to SMT storage. \ No newline at end of file +- [`v5.0.0`](https://github.com/cosmos/ibc-go/milestone/21): Final release that includes the update of Cosmos SDK from `v0.45` to [v1.0](https://github.com/cosmos/cosmos-sdk/milestone/52) and that will support the migration to SMT storage. \ No newline at end of file diff --git a/go.mod b/go.mod index ae2d93ad884..3e5f606755d 100644 --- a/go.mod +++ b/go.mod @@ -54,6 +54,7 @@ require ( github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b // indirect github.com/felixge/httpsnoop v1.0.1 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect + github.com/gin-gonic/gin v1.7.0 // indirect github.com/go-kit/kit v0.10.0 // indirect github.com/go-logfmt/logfmt v0.5.0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect @@ -86,6 +87,8 @@ require ( github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.4.3 // indirect github.com/mtibben/percent v0.2.1 // indirect + github.com/opencontainers/image-spec v1.0.2 // indirect + github.com/opencontainers/runc v1.0.3 // indirect github.com/pelletier/go-toml v1.9.4 // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index 429f21379be..8342959fd77 100644 --- a/go.sum +++ b/go.sum @@ -298,8 +298,9 @@ github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/gin-gonic/gin v1.7.0 h1:jGB9xAJQ12AIGNB4HguylppmDK1Am9ppF7XnGXXJuoU= +github.com/gin-gonic/gin v1.7.0/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= 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= @@ -318,8 +319,9 @@ github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8c github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= @@ -681,11 +683,13 @@ github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je4 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= +github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.2 h1:opHZMaswlyxz1OuGpBE53Dwe4/xF7EZTY0A2L/FpCOg= github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= +github.com/opencontainers/runc v1.0.3 h1:1hbqejyQWCJBvtKAfdO0b1FmaEf2z/bxnjqbARass5k= +github.com/opencontainers/runc v1.0.3/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= diff --git a/modules/apps/27-interchain-accounts/controller/client/cli/query.go b/modules/apps/27-interchain-accounts/controller/client/cli/query.go index 3be2cf12704..2f06284a28c 100644 --- a/modules/apps/27-interchain-accounts/controller/client/cli/query.go +++ b/modules/apps/27-interchain-accounts/controller/client/cli/query.go @@ -26,7 +26,11 @@ func GetCmdParams() *cobra.Command { } queryClient := types.NewQueryClient(clientCtx) - res, _ := queryClient.Params(cmd.Context(), &types.QueryParamsRequest{}) + res, err := queryClient.Params(cmd.Context(), &types.QueryParamsRequest{}) + if err != nil { + return err + } + return clientCtx.PrintProto(res.Params) }, } diff --git a/modules/apps/27-interchain-accounts/controller/ibc_module.go b/modules/apps/27-interchain-accounts/controller/ibc_module.go index dff1456d20c..1aa362a4247 100644 --- a/modules/apps/27-interchain-accounts/controller/ibc_module.go +++ b/modules/apps/27-interchain-accounts/controller/ibc_module.go @@ -65,10 +65,9 @@ func (im IBCModule) OnChanOpenTry( channelID string, chanCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, - version, counterpartyVersion string, -) error { - return sdkerrors.Wrap(icatypes.ErrInvalidChannelFlow, "channel handshake must be initiated by controller chain") +) (string, error) { + return "", sdkerrors.Wrap(icatypes.ErrInvalidChannelFlow, "channel handshake must be initiated by controller chain") } // OnChanOpenAck implements the IBCModule interface @@ -163,15 +162,3 @@ func (im IBCModule) OnTimeoutPacket( return im.app.OnTimeoutPacket(ctx, packet, relayer) } - -// NegotiateAppVersion implements the IBCModule interface -func (im IBCModule) NegotiateAppVersion( - ctx sdk.Context, - order channeltypes.Order, - connectionID string, - portID string, - counterparty channeltypes.Counterparty, - proposedVersion string, -) (string, error) { - return "", sdkerrors.Wrap(icatypes.ErrInvalidChannelFlow, "ICS-27 app version negotiation is unsupported on controller chains") -} diff --git a/modules/apps/27-interchain-accounts/controller/ibc_module_test.go b/modules/apps/27-interchain-accounts/controller/ibc_module_test.go index ea4fc108f49..5137606e764 100644 --- a/modules/apps/27-interchain-accounts/controller/ibc_module_test.go +++ b/modules/apps/27-interchain-accounts/controller/ibc_module_test.go @@ -236,12 +236,13 @@ func (suite *InterchainAccountsTestSuite) TestChanOpenTry() { chanCap, found := suite.chainA.App.GetScopedIBCKeeper().GetCapability(suite.chainA.GetContext(), host.ChannelCapabilityPath(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)) suite.Require().True(found) - err = cbs.OnChanOpenTry( + version, err := cbs.OnChanOpenTry( suite.chainA.GetContext(), path.EndpointA.ChannelConfig.Order, []string{path.EndpointA.ConnectionID}, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, chanCap, - counterparty, path.EndpointA.ChannelConfig.Version, path.EndpointB.ChannelConfig.Version, + counterparty, path.EndpointB.ChannelConfig.Version, ) suite.Require().Error(err) + suite.Require().Equal("", version) } func (suite *InterchainAccountsTestSuite) TestOnChanOpenAck() { @@ -630,59 +631,3 @@ func (suite *InterchainAccountsTestSuite) TestOnTimeoutPacket() { }) } } - -func (suite *InterchainAccountsTestSuite) TestNegotiateAppVersion() { - var ( - proposedVersion string - ) - testCases := []struct { - name string - malleate func() - expPass bool - }{ - { - "ICA OnRecvPacket fails with ErrInvalidChannelFlow", func() {}, false, - }, - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - suite.SetupTest() - path := NewICAPath(suite.chainA, suite.chainB) - suite.coordinator.SetupConnections(path) - - err := InitInterchainAccount(path.EndpointA, TestOwnerAddress) - suite.Require().NoError(err) - - module, _, err := suite.chainA.GetSimApp().GetIBCKeeper().PortKeeper.LookupModuleByPort(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID) - suite.Require().NoError(err) - - cbs, ok := suite.chainA.GetSimApp().GetIBCKeeper().Router.GetRoute(module) - suite.Require().True(ok) - - counterpartyPortID, err := icatypes.GeneratePortID(TestOwnerAddress, path.EndpointA.ConnectionID, path.EndpointB.ConnectionID) - suite.Require().NoError(err) - - counterparty := channeltypes.Counterparty{ - PortId: counterpartyPortID, - ChannelId: path.EndpointB.ChannelID, - } - - proposedVersion = icatypes.VersionPrefix - - tc.malleate() - - version, err := cbs.NegotiateAppVersion(suite.chainA.GetContext(), channeltypes.ORDERED, path.EndpointA.ConnectionID, path.EndpointA.ChannelConfig.PortID, counterparty, proposedVersion) - if tc.expPass { - suite.Require().NoError(err) - suite.Require().NoError(icatypes.ValidateVersion(version)) - suite.Require().Equal(TestVersion, version) - } else { - suite.Require().Error(err) - suite.Require().Empty(version) - } - }) - } -} diff --git a/modules/apps/27-interchain-accounts/controller/keeper/grpc_query.go b/modules/apps/27-interchain-accounts/controller/keeper/grpc_query.go index deb5cfae106..dd1d04a96b3 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/grpc_query.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/grpc_query.go @@ -2,7 +2,9 @@ package keeper import ( "context" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/controller/types" ) diff --git a/modules/apps/27-interchain-accounts/controller/keeper/handshake.go b/modules/apps/27-interchain-accounts/controller/keeper/handshake.go index 66f0720f66c..ea0b907569a 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/handshake.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/handshake.go @@ -42,7 +42,7 @@ func (k Keeper) OnChanOpenInit( return sdkerrors.Wrapf(err, "expected format %s, got %s", icatypes.ControllerPortFormat, portID) } - if err := k.validateControllerPortParams(ctx, channelID, portID, connSequence, counterpartyConnSequence); err != nil { + if err := k.validateControllerPortParams(ctx, connectionHops, connSequence, counterpartyConnSequence); err != nil { return sdkerrors.Wrapf(err, "failed to validate controller port %s", portID) } @@ -104,8 +104,9 @@ func (k Keeper) OnChanCloseConfirm( // validateControllerPortParams asserts the provided connection sequence and counterparty connection sequence // match that of the associated connection stored in state -func (k Keeper) validateControllerPortParams(ctx sdk.Context, channelID, portID string, connectionSeq, counterpartyConnectionSeq uint64) error { - connectionID, connection, err := k.channelKeeper.GetChannelConnection(ctx, portID, channelID) +func (k Keeper) validateControllerPortParams(ctx sdk.Context, connectionHops []string, connectionSeq, counterpartyConnectionSeq uint64) error { + connectionID := connectionHops[0] + connection, err := k.channelKeeper.GetConnection(ctx, connectionID) if err != nil { return err } diff --git a/modules/apps/27-interchain-accounts/controller/keeper/handshake_test.go b/modules/apps/27-interchain-accounts/controller/keeper/handshake_test.go index c121447cf3c..3df200e51a3 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/handshake_test.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/handshake_test.go @@ -59,13 +59,6 @@ func (suite *KeeperTestSuite) TestOnChanOpenInit() { }, false, }, - { - "channel not found", - func() { - path.EndpointA.ChannelID = "invalid-channel-id" - }, - false, - }, { "connection not found", func() { diff --git a/modules/apps/27-interchain-accounts/host/client/cli/query.go b/modules/apps/27-interchain-accounts/host/client/cli/query.go index e00b337c45a..0033e8f9e7c 100644 --- a/modules/apps/27-interchain-accounts/host/client/cli/query.go +++ b/modules/apps/27-interchain-accounts/host/client/cli/query.go @@ -26,7 +26,11 @@ func GetCmdParams() *cobra.Command { } queryClient := types.NewQueryClient(clientCtx) - res, _ := queryClient.Params(cmd.Context(), &types.QueryParamsRequest{}) + res, err := queryClient.Params(cmd.Context(), &types.QueryParamsRequest{}) + if err != nil { + return err + } + return clientCtx.PrintProto(res.Params) }, } diff --git a/modules/apps/27-interchain-accounts/host/ibc_module.go b/modules/apps/27-interchain-accounts/host/ibc_module.go index 91513dd434f..bf847349c69 100644 --- a/modules/apps/27-interchain-accounts/host/ibc_module.go +++ b/modules/apps/27-interchain-accounts/host/ibc_module.go @@ -47,14 +47,13 @@ func (im IBCModule) OnChanOpenTry( channelID string, chanCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, - version, counterpartyVersion string, -) error { +) (string, error) { if !im.keeper.IsHostEnabled(ctx) { - return types.ErrHostSubModuleDisabled + return "", types.ErrHostSubModuleDisabled } - return im.keeper.OnChanOpenTry(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, version, counterpartyVersion) + return im.keeper.OnChanOpenTry(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, counterpartyVersion) } // OnChanOpenAck implements the IBCModule interface @@ -137,15 +136,3 @@ func (im IBCModule) OnTimeoutPacket( ) error { return sdkerrors.Wrap(icatypes.ErrInvalidChannelFlow, "cannot cause a packet timeout on a host channel end, a host chain does not send a packet over the channel") } - -// NegotiateAppVersion implements the IBCModule interface -func (im IBCModule) NegotiateAppVersion( - ctx sdk.Context, - order channeltypes.Order, - connectionID string, - portID string, - counterparty channeltypes.Counterparty, - proposedVersion string, -) (string, error) { - return im.keeper.NegotiateAppVersion(ctx, order, connectionID, portID, counterparty, proposedVersion) -} diff --git a/modules/apps/27-interchain-accounts/host/ibc_module_test.go b/modules/apps/27-interchain-accounts/host/ibc_module_test.go index 245f6dbac63..1f76d661a6c 100644 --- a/modules/apps/27-interchain-accounts/host/ibc_module_test.go +++ b/modules/apps/27-interchain-accounts/host/ibc_module_test.go @@ -147,16 +147,16 @@ func (suite *InterchainAccountsTestSuite) TestOnChanOpenTry() { // 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, chanCap *capabilitytypes.Capability, - counterparty channeltypes.Counterparty, version, counterpartyVersion string, - ) error { - return fmt.Errorf("mock ica auth fails") + counterparty channeltypes.Counterparty, counterpartyVersion string, + ) (string, error) { + return "", fmt.Errorf("mock ica auth fails") } }, true, }, { - "ICA callback fails - invalid version", func() { - channel.Version = icatypes.VersionPrefix + "ICA callback fails - invalid channel order", func() { + channel.Ordering = channeltypes.UNORDERED }, false, }, } @@ -181,7 +181,7 @@ func (suite *InterchainAccountsTestSuite) TestOnChanOpenTry() { Ordering: channeltypes.ORDERED, Counterparty: counterparty, ConnectionHops: []string{path.EndpointB.ConnectionID}, - Version: TestVersion, + Version: "", } tc.malleate() @@ -198,14 +198,16 @@ func (suite *InterchainAccountsTestSuite) TestOnChanOpenTry() { cbs, ok := suite.chainB.App.GetIBCKeeper().Router.GetRoute(module) suite.Require().True(ok) - err = cbs.OnChanOpenTry(suite.chainB.GetContext(), channel.Ordering, channel.GetConnectionHops(), - path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, chanCap, channel.Counterparty, channel.GetVersion(), path.EndpointA.ChannelConfig.Version, + version, err := cbs.OnChanOpenTry(suite.chainB.GetContext(), channel.Ordering, channel.GetConnectionHops(), + path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, chanCap, channel.Counterparty, path.EndpointA.ChannelConfig.Version, ) if tc.expPass { suite.Require().NoError(err) + suite.Require().Equal(TestVersion, version) } else { suite.Require().Error(err) + suite.Require().Equal("", version) } }) @@ -584,61 +586,3 @@ func (suite *InterchainAccountsTestSuite) TestOnTimeoutPacket() { }) } } - -func (suite *InterchainAccountsTestSuite) TestNegotiateAppVersion() { - var ( - proposedVersion string - ) - testCases := []struct { - name string - malleate func() - expPass bool - }{ - { - "success", func() {}, true, - }, - { - "invalid proposed version", func() { - proposedVersion = "invalid version" - }, false, - }, - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - suite.SetupTest() - path := NewICAPath(suite.chainA, suite.chainB) - suite.coordinator.SetupConnections(path) - - module, _, err := suite.chainA.GetSimApp().GetIBCKeeper().PortKeeper.LookupModuleByPort(suite.chainA.GetContext(), icatypes.PortID) - suite.Require().NoError(err) - - cbs, ok := suite.chainA.GetSimApp().GetIBCKeeper().Router.GetRoute(module) - suite.Require().True(ok) - - counterpartyPortID, err := icatypes.GeneratePortID(TestOwnerAddress, path.EndpointA.ConnectionID, path.EndpointB.ConnectionID) - suite.Require().NoError(err) - - counterparty := &channeltypes.Counterparty{ - PortId: counterpartyPortID, - ChannelId: path.EndpointB.ChannelID, - } - - proposedVersion = icatypes.VersionPrefix - - tc.malleate() - - version, err := cbs.NegotiateAppVersion(suite.chainA.GetContext(), channeltypes.ORDERED, path.EndpointA.ConnectionID, icatypes.PortID, *counterparty, proposedVersion) - if tc.expPass { - suite.Require().NoError(err) - suite.Require().NoError(icatypes.ValidateVersion(version)) - suite.Require().Equal(TestVersion, version) - } else { - suite.Require().Error(err) - suite.Require().Empty(version) - } - }) - } -} diff --git a/modules/apps/27-interchain-accounts/host/keeper/handshake.go b/modules/apps/27-interchain-accounts/host/keeper/handshake.go index 3c19c931122..bb65da4a33b 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/handshake.go +++ b/modules/apps/27-interchain-accounts/host/keeper/handshake.go @@ -14,6 +14,8 @@ import ( // 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, @@ -22,60 +24,47 @@ func (k Keeper) OnChanOpenTry( channelID string, chanCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, - version, counterpartyVersion string, -) error { +) (string, error) { if order != channeltypes.ORDERED { - return sdkerrors.Wrapf(channeltypes.ErrInvalidChannelOrdering, "expected %s channel, got %s", channeltypes.ORDERED, order) + return "", sdkerrors.Wrapf(channeltypes.ErrInvalidChannelOrdering, "expected %s channel, got %s", channeltypes.ORDERED, order) } if portID != icatypes.PortID { - return sdkerrors.Wrapf(porttypes.ErrInvalidPort, "expected %s, got %s", icatypes.PortID, portID) + return "", sdkerrors.Wrapf(porttypes.ErrInvalidPort, "expected %s, got %s", icatypes.PortID, portID) } connSequence, err := icatypes.ParseHostConnSequence(counterparty.PortId) if err != nil { - return sdkerrors.Wrapf(err, "expected format %s, got %s", icatypes.ControllerPortFormat, counterparty.PortId) + return "", sdkerrors.Wrapf(err, "expected format %s, got %s", icatypes.ControllerPortFormat, counterparty.PortId) } counterpartyConnSequence, err := icatypes.ParseControllerConnSequence(counterparty.PortId) if err != nil { - return sdkerrors.Wrapf(err, "expected format %s, got %s", icatypes.ControllerPortFormat, counterparty.PortId) - } - - if err := k.validateControllerPortParams(ctx, channelID, portID, connSequence, counterpartyConnSequence); err != nil { - return sdkerrors.Wrapf(err, "failed to validate controller port %s", counterparty.PortId) + return "", sdkerrors.Wrapf(err, "expected format %s, got %s", icatypes.ControllerPortFormat, counterparty.PortId) } - if err := icatypes.ValidateVersion(version); err != nil { - return sdkerrors.Wrap(err, "version validation failed") + if err := k.validateControllerPortParams(ctx, connectionHops, connSequence, counterpartyConnSequence); err != nil { + return "", sdkerrors.Wrapf(err, "failed to validate controller port %s", counterparty.PortId) } if counterpartyVersion != icatypes.VersionPrefix { - return sdkerrors.Wrapf(icatypes.ErrInvalidVersion, "expected %s, got %s", icatypes.VersionPrefix, version) + return "", sdkerrors.Wrapf(icatypes.ErrInvalidVersion, "expected %s, got %s", icatypes.VersionPrefix, counterpartyVersion) } // On the host chain the capability may only be claimed during the OnChanOpenTry // The capability being claimed in OpenInit is for a controller chain (the port is different) if err := k.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { - return sdkerrors.Wrapf(err, "failed to claim capability for channel %s on port %s", channelID, portID) + return "", sdkerrors.Wrapf(err, "failed to claim capability for channel %s on port %s", channelID, portID) } - // Check to ensure that the version string contains the expected address generated from the Counterparty portID accAddr := icatypes.GenerateAddress(k.accountKeeper.GetModuleAddress(icatypes.ModuleName), counterparty.PortId) - parsedAddr, err := icatypes.ParseAddressFromVersion(version) - if err != nil { - return sdkerrors.Wrapf(err, "expected format , got %s", icatypes.Delimiter, version) - } - - if parsedAddr != accAddr.String() { - return sdkerrors.Wrapf(icatypes.ErrInvalidVersion, "version contains invalid account address: expected %s, got %s", parsedAddr, accAddr) - } // Register interchain account if it does not already exist k.RegisterInterchainAccount(ctx, accAddr, counterparty.PortId) + version := icatypes.NewAppVersion(icatypes.VersionPrefix, accAddr.String()) - return nil + return version, nil } // OnChanOpenConfirm completes the handshake process by setting the active channel in state on the host chain @@ -104,8 +93,9 @@ func (k Keeper) OnChanCloseConfirm( // validateControllerPortParams asserts the provided connection sequence and counterparty connection sequence // match that of the associated connection stored in state -func (k Keeper) validateControllerPortParams(ctx sdk.Context, channelID, portID string, connectionSeq, counterpartyConnectionSeq uint64) error { - connectionID, connection, err := k.channelKeeper.GetChannelConnection(ctx, portID, channelID) +func (k Keeper) validateControllerPortParams(ctx sdk.Context, connectionHops []string, connectionSeq, counterpartyConnectionSeq uint64) error { + connectionID := connectionHops[0] + connection, err := k.channelKeeper.GetConnection(ctx, connectionID) if err != nil { return err } diff --git a/modules/apps/27-interchain-accounts/host/keeper/handshake_test.go b/modules/apps/27-interchain-accounts/host/keeper/handshake_test.go index 23b7b88b58f..48179f1a8a0 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/handshake_test.go +++ b/modules/apps/27-interchain-accounts/host/keeper/handshake_test.go @@ -51,13 +51,6 @@ func (suite *KeeperTestSuite) TestOnChanOpenTry() { }, false, }, - { - "channel not found", - func() { - path.EndpointB.ChannelID = "invalid-channel-id" - }, - false, - }, { "connection not found", func() { @@ -88,14 +81,6 @@ func (suite *KeeperTestSuite) TestOnChanOpenTry() { }, false, }, - { - "invalid version", - func() { - channel.Version = "version" - path.EndpointB.SetChannel(*channel) - }, - false, - }, { "invalid counterparty version", func() { @@ -113,17 +98,6 @@ func (suite *KeeperTestSuite) TestOnChanOpenTry() { }, false, }, - { - "invalid account address", - func() { - portID, err := icatypes.GeneratePortID("invalid-owner-addr", "connection-0", "connection-0") - suite.Require().NoError(err) - - channel.Counterparty.PortId = portID - path.EndpointB.SetChannel(*channel) - }, - false, - }, } for _, tc := range testCases { @@ -158,15 +132,16 @@ func (suite *KeeperTestSuite) TestOnChanOpenTry() { tc.malleate() // malleate mutates test data - err = suite.chainB.GetSimApp().ICAHostKeeper.OnChanOpenTry(suite.chainB.GetContext(), channel.Ordering, channel.GetConnectionHops(), - path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, chanCap, channel.Counterparty, channel.GetVersion(), - counterpartyVersion, + version, err := suite.chainB.GetSimApp().ICAHostKeeper.OnChanOpenTry(suite.chainB.GetContext(), channel.Ordering, channel.GetConnectionHops(), + path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, chanCap, channel.Counterparty, counterpartyVersion, ) if tc.expPass { suite.Require().NoError(err) + suite.Require().Equal(TestVersion, version) } else { suite.Require().Error(err) + suite.Require().Equal("", version) } }) } diff --git a/modules/apps/27-interchain-accounts/host/keeper/keeper.go b/modules/apps/27-interchain-accounts/host/keeper/keeper.go index b9a557726ee..5d8569a6e1a 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/keeper.go +++ b/modules/apps/27-interchain-accounts/host/keeper/keeper.go @@ -7,7 +7,6 @@ import ( baseapp "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" @@ -15,7 +14,6 @@ import ( "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/host/types" icatypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/types" - channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" host "github.com/cosmos/ibc-go/v3/modules/core/24-host" ) @@ -180,22 +178,3 @@ func (k Keeper) SetInterchainAccountAddress(ctx sdk.Context, portID string, addr store := ctx.KVStore(k.storeKey) store.Set(icatypes.KeyOwnerAccount(portID), []byte(address)) } - -// NegotiateAppVersion handles application version negotation for the IBC interchain accounts module -func (k Keeper) NegotiateAppVersion( - ctx sdk.Context, - order channeltypes.Order, - connectionID string, - portID string, - counterparty channeltypes.Counterparty, - proposedVersion string, -) (string, error) { - if proposedVersion != icatypes.VersionPrefix { - return "", sdkerrors.Wrapf(icatypes.ErrInvalidVersion, "failed to negotiate app version: expected %s, got %s", icatypes.VersionPrefix, proposedVersion) - } - - moduleAccAddr := k.accountKeeper.GetModuleAddress(icatypes.ModuleName) - accAddr := icatypes.GenerateAddress(moduleAccAddr, counterparty.PortId) - - return icatypes.NewAppVersion(icatypes.VersionPrefix, accAddr.String()), nil -} diff --git a/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go b/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go index e8cffcdb640..102cb278ebe 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go +++ b/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go @@ -50,7 +50,7 @@ func NewICAPath(chainA, chainB *ibctesting.TestChain) *ibctesting.Path { path.EndpointA.ChannelConfig.Order = channeltypes.ORDERED path.EndpointB.ChannelConfig.Order = channeltypes.ORDERED path.EndpointA.ChannelConfig.Version = icatypes.VersionPrefix - path.EndpointB.ChannelConfig.Version = TestVersion + path.EndpointB.ChannelConfig.Version = icatypes.VersionPrefix return path } diff --git a/modules/apps/27-interchain-accounts/host/keeper/relay_test.go b/modules/apps/27-interchain-accounts/host/keeper/relay_test.go index b8e4ec54ba7..ef8709b5ce1 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/relay_test.go +++ b/modules/apps/27-interchain-accounts/host/keeper/relay_test.go @@ -250,6 +250,8 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() { transferPath := ibctesting.NewPath(suite.chainB, suite.chainC) transferPath.EndpointA.ChannelConfig.PortID = ibctesting.TransferPort transferPath.EndpointB.ChannelConfig.PortID = ibctesting.TransferPort + transferPath.EndpointA.ChannelConfig.Version = transfertypes.Version + transferPath.EndpointB.ChannelConfig.Version = transfertypes.Version suite.coordinator.Setup(transferPath) diff --git a/modules/apps/27-interchain-accounts/module.go b/modules/apps/27-interchain-accounts/module.go index a918c552b40..1f816a50d1f 100644 --- a/modules/apps/27-interchain-accounts/module.go +++ b/modules/apps/27-interchain-accounts/module.go @@ -130,6 +130,8 @@ func (am AppModule) LegacyQuerierHandler(legacyQuerierCdc *codec.LegacyAmino) sd // RegisterServices registers module services func (am AppModule) RegisterServices(cfg module.Configurator) { + controllertypes.RegisterQueryServer(cfg.QueryServer(), am.controllerKeeper) + hosttypes.RegisterQueryServer(cfg.QueryServer(), am.hostKeeper) } // InitGenesis performs genesis initialization for the interchain accounts module. diff --git a/modules/apps/27-interchain-accounts/types/codec.go b/modules/apps/27-interchain-accounts/types/codec.go index 971554a83e4..2ac1f553bcc 100644 --- a/modules/apps/27-interchain-accounts/types/codec.go +++ b/modules/apps/27-interchain-accounts/types/codec.go @@ -23,10 +23,11 @@ func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { cdc.RegisterConcrete(&InterchainAccount{}, "27-interchain-accounts/InterchainAccount", nil) } -// RegisterInterface associates protoName with AccountI interface -// and creates a registry of it's concrete implementations +// RegisterInterfaces registers the concrete InterchainAccount implementation against the associated +// x/auth AccountI and GenesisAccount interfaces func RegisterInterfaces(registry codectypes.InterfaceRegistry) { registry.RegisterImplementations((*authtypes.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 diff --git a/modules/apps/27-interchain-accounts/types/expected_keepers.go b/modules/apps/27-interchain-accounts/types/expected_keepers.go index ba782e1d2f1..4c6a1708e43 100644 --- a/modules/apps/27-interchain-accounts/types/expected_keepers.go +++ b/modules/apps/27-interchain-accounts/types/expected_keepers.go @@ -27,7 +27,7 @@ type ICS4Wrapper interface { type ChannelKeeper interface { GetChannel(ctx sdk.Context, srcPort, srcChan string) (channel channeltypes.Channel, found bool) GetNextSequenceSend(ctx sdk.Context, portID, channelID string) (uint64, bool) - GetChannelConnection(ctx sdk.Context, portID, channelID string) (string, ibcexported.ConnectionI, error) + GetConnection(ctx sdk.Context, connectionID string) (ibcexported.ConnectionI, error) } // PortKeeper defines the expected IBC port keeper diff --git a/modules/apps/transfer/client/cli/query.go b/modules/apps/transfer/client/cli/query.go index 5d0a947ee60..78d7bfd1b99 100644 --- a/modules/apps/transfer/client/cli/query.go +++ b/modules/apps/transfer/client/cli/query.go @@ -97,7 +97,11 @@ func GetCmdParams() *cobra.Command { } queryClient := types.NewQueryClient(clientCtx) - res, _ := queryClient.Params(cmd.Context(), &types.QueryParamsRequest{}) + res, err := queryClient.Params(cmd.Context(), &types.QueryParamsRequest{}) + if err != nil { + return err + } + return clientCtx.PrintProto(res.Params) }, } diff --git a/modules/apps/transfer/ibc_module.go b/modules/apps/transfer/ibc_module.go index 5956fa585ad..1ad67d16a85 100644 --- a/modules/apps/transfer/ibc_module.go +++ b/modules/apps/transfer/ibc_module.go @@ -37,7 +37,6 @@ func ValidateTransferChannelParams( order channeltypes.Order, portID string, channelID string, - version 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 @@ -58,9 +57,6 @@ func ValidateTransferChannelParams( return sdkerrors.Wrapf(porttypes.ErrInvalidPort, "invalid port: %s, expected %s", portID, boundPort) } - if version != types.Version { - return sdkerrors.Wrapf(types.ErrInvalidVersion, "got %s, expected %s", version, types.Version) - } return nil } @@ -75,10 +71,14 @@ func (im IBCModule) OnChanOpenInit( counterparty channeltypes.Counterparty, version string, ) error { - if err := ValidateTransferChannelParams(ctx, im.keeper, order, portID, channelID, version); err != nil { + if err := ValidateTransferChannelParams(ctx, im.keeper, order, portID, channelID); err != nil { return err } + if version != types.Version { + return sdkerrors.Wrapf(types.ErrInvalidVersion, "got %s, expected %s", version, types.Version) + } + // Claim channel capability passed back by IBC module if err := im.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { return err @@ -87,7 +87,7 @@ func (im IBCModule) OnChanOpenInit( return nil } -// OnChanOpenTry implements the IBCModule interface +// OnChanOpenTry implements the IBCModule interface. func (im IBCModule) OnChanOpenTry( ctx sdk.Context, order channeltypes.Order, @@ -96,15 +96,14 @@ func (im IBCModule) OnChanOpenTry( channelID string, chanCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, - version, counterpartyVersion string, -) error { - if err := ValidateTransferChannelParams(ctx, im.keeper, order, portID, channelID, version); err != nil { - return err +) (string, error) { + if err := ValidateTransferChannelParams(ctx, im.keeper, order, portID, channelID); err != nil { + return "", err } if counterpartyVersion != types.Version { - return sdkerrors.Wrapf(types.ErrInvalidVersion, "invalid counterparty version: got: %s, expected %s", counterpartyVersion, types.Version) + return "", sdkerrors.Wrapf(types.ErrInvalidVersion, "invalid counterparty version: got: %s, expected %s", counterpartyVersion, types.Version) } // Module may have already claimed capability in OnChanOpenInit in the case of crossing hellos @@ -114,11 +113,11 @@ func (im IBCModule) OnChanOpenTry( if !im.keeper.AuthenticateCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)) { // Only claim channel capability passed back by IBC module if we do not already own it if err := im.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { - return err + return "", err } } - return nil + return types.Version, nil } // OnChanOpenAck implements the IBCModule interface @@ -279,19 +278,3 @@ func (im IBCModule) OnTimeoutPacket( return nil } - -// NegotiateAppVersion implements the IBCModule interface -func (im IBCModule) NegotiateAppVersion( - ctx sdk.Context, - order channeltypes.Order, - connectionID string, - portID string, - counterparty channeltypes.Counterparty, - proposedVersion string, -) (string, error) { - if proposedVersion != types.Version { - return "", sdkerrors.Wrapf(types.ErrInvalidVersion, "failed to negotiate app version: expected %s, got %s", types.Version, proposedVersion) - } - - return types.Version, nil -} diff --git a/modules/apps/transfer/ibc_module_test.go b/modules/apps/transfer/ibc_module_test.go index 7ab67b0af06..9ffd7ddcf4f 100644 --- a/modules/apps/transfer/ibc_module_test.go +++ b/modules/apps/transfer/ibc_module_test.go @@ -135,11 +135,6 @@ func (suite *TransferTestSuite) TestOnChanOpenTry() { path.EndpointA.ChannelConfig.PortID = ibctesting.MockPort }, false, }, - { - "invalid version", func() { - channel.Version = "version" - }, false, - }, { "invalid counterparty version", func() { counterpartyVersion = "version" @@ -178,14 +173,16 @@ func (suite *TransferTestSuite) TestOnChanOpenTry() { tc.malleate() // explicitly change fields in channel and testChannel - err = cbs.OnChanOpenTry(suite.chainA.GetContext(), channel.Ordering, channel.GetConnectionHops(), - path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, chanCap, channel.Counterparty, channel.GetVersion(), counterpartyVersion, + version, err := cbs.OnChanOpenTry(suite.chainA.GetContext(), channel.Ordering, channel.GetConnectionHops(), + path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, chanCap, channel.Counterparty, counterpartyVersion, ) if tc.expPass { suite.Require().NoError(err) + suite.Require().Equal(types.Version, version) } else { suite.Require().Error(err) + suite.Require().Equal("", version) } }) diff --git a/modules/apps/transfer/keeper/keeper.go b/modules/apps/transfer/keeper/keeper.go index c37f39335ce..0c96e2219d2 100644 --- a/modules/apps/transfer/keeper/keeper.go +++ b/modules/apps/transfer/keeper/keeper.go @@ -21,6 +21,7 @@ type Keeper struct { cdc codec.BinaryCodec paramSpace paramtypes.Subspace + ics4Wrapper types.ICS4Wrapper channelKeeper types.ChannelKeeper portKeeper types.PortKeeper authKeeper types.AccountKeeper @@ -31,7 +32,7 @@ type Keeper struct { // NewKeeper creates a new IBC transfer Keeper instance func NewKeeper( cdc codec.BinaryCodec, key sdk.StoreKey, paramSpace paramtypes.Subspace, - channelKeeper types.ChannelKeeper, portKeeper types.PortKeeper, + ics4Wrapper types.ICS4Wrapper, channelKeeper types.ChannelKeeper, portKeeper types.PortKeeper, authKeeper types.AccountKeeper, bankKeeper types.BankKeeper, scopedKeeper capabilitykeeper.ScopedKeeper, ) Keeper { @@ -49,6 +50,7 @@ func NewKeeper( cdc: cdc, storeKey: key, paramSpace: paramSpace, + ics4Wrapper: ics4Wrapper, channelKeeper: channelKeeper, portKeeper: portKeeper, authKeeper: authKeeper, diff --git a/modules/apps/transfer/keeper/keeper_test.go b/modules/apps/transfer/keeper/keeper_test.go index ef748757588..0fdb1121387 100644 --- a/modules/apps/transfer/keeper/keeper_test.go +++ b/modules/apps/transfer/keeper/keeper_test.go @@ -40,6 +40,8 @@ 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 + path.EndpointA.ChannelConfig.Version = types.Version + path.EndpointB.ChannelConfig.Version = types.Version return path } diff --git a/modules/apps/transfer/keeper/relay.go b/modules/apps/transfer/keeper/relay.go index 409dfc5f130..ab7f3751588 100644 --- a/modules/apps/transfer/keeper/relay.go +++ b/modules/apps/transfer/keeper/relay.go @@ -158,7 +158,7 @@ func (k Keeper) SendTransfer( timeoutTimestamp, ) - if err := k.channelKeeper.SendPacket(ctx, channelCap, packet); err != nil { + if err := k.ics4Wrapper.SendPacket(ctx, channelCap, packet); err != nil { return err } diff --git a/modules/apps/transfer/transfer_test.go b/modules/apps/transfer/transfer_test.go index d2f822c83a2..fc16aa39a28 100644 --- a/modules/apps/transfer/transfer_test.go +++ b/modules/apps/transfer/transfer_test.go @@ -34,6 +34,8 @@ 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 + path.EndpointA.ChannelConfig.Version = types.Version + path.EndpointB.ChannelConfig.Version = types.Version return path } diff --git a/modules/apps/transfer/types/expected_keepers.go b/modules/apps/transfer/types/expected_keepers.go index 3bdaccb749a..8f4503d0872 100644 --- a/modules/apps/transfer/types/expected_keepers.go +++ b/modules/apps/transfer/types/expected_keepers.go @@ -24,11 +24,15 @@ type BankKeeper interface { SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error } +// ICS4Wrapper defines the expected ICS4Wrapper for middleware +type ICS4Wrapper interface { + SendPacket(ctx sdk.Context, channelCap *capabilitytypes.Capability, packet ibcexported.PacketI) error +} + // 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) - SendPacket(ctx sdk.Context, channelCap *capabilitytypes.Capability, packet ibcexported.PacketI) error } // ClientKeeper defines the expected IBC client keeper diff --git a/modules/core/04-channel/keeper/handshake.go b/modules/core/04-channel/keeper/handshake.go index f1fa5eafcb6..a9c5046c099 100644 --- a/modules/core/04-channel/keeper/handshake.go +++ b/modules/core/04-channel/keeper/handshake.go @@ -1,6 +1,8 @@ package keeper import ( + "fmt" + "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -53,14 +55,30 @@ func (k Keeper) ChanOpenInit( } channelID := k.GenerateChannelIdentifier(ctx) - channel := types.NewChannel(types.INIT, order, counterparty, connectionHops, version) - k.SetChannel(ctx, portID, channelID, channel) capKey, err := k.scopedKeeper.NewCapability(ctx, host.ChannelCapabilityPath(portID, channelID)) if err != nil { return "", nil, sdkerrors.Wrapf(err, "could not create channel capability for port ID %s and channel ID %s", portID, channelID) } + return channelID, capKey, 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) @@ -82,7 +100,12 @@ func (k Keeper) ChanOpenInit( ), }) - return channelID, capKey, nil + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) } // ChanOpenTry is called by a module to accept the first step of a channel opening @@ -95,7 +118,6 @@ func (k Keeper) ChanOpenTry( previousChannelID string, portCap *capabilitytypes.Capability, counterparty types.Counterparty, - version, counterpartyVersion string, proofInit []byte, proofHeight exported.Height, @@ -125,7 +147,7 @@ func (k Keeper) ChanOpenTry( previousChannel.Counterparty.PortId == counterparty.PortId && previousChannel.Counterparty.ChannelId == "" && previousChannel.ConnectionHops[0] == connectionHops[0] && // ChanOpenInit will only set a single connection hop - previousChannel.Version == version) { + previousChannel.Version == counterpartyVersion) { return "", nil, sdkerrors.Wrap(types.ErrInvalidChannel, "channel fields mismatch previous channel fields") } @@ -171,16 +193,13 @@ func (k Keeper) ChanOpenTry( ) } - // NOTE: this step has been switched with the one below to reverse the connection - // hops - channel := types.NewChannel(types.TRYOPEN, order, counterparty, connectionHops, version) counterpartyHops := []string{connectionEnd.GetCounterparty().GetConnectionID()} // expectedCounterpaty is the counterparty of the counterparty's channel end // (i.e self) expectedCounterparty := types.NewCounterparty(portID, "") expectedChannel := types.NewChannel( - types.INIT, channel.Ordering, expectedCounterparty, + types.INIT, order, expectedCounterparty, counterpartyHops, counterpartyVersion, ) @@ -202,9 +221,6 @@ func (k Keeper) ChanOpenTry( return "", nil, sdkerrors.Wrapf(err, "could not create channel capability for port ID %s and channel ID %s", portID, channelID) } - k.SetNextSequenceSend(ctx, portID, channelID, 1) - k.SetNextSequenceRecv(ctx, portID, channelID, 1) - k.SetNextSequenceAck(ctx, portID, channelID, 1) } else { // capability initialized in ChanOpenInit capKey, found = k.scopedKeeper.GetCapability(ctx, host.ChannelCapabilityPath(portID, channelID)) @@ -215,6 +231,30 @@ func (k Keeper) ChanOpenTry( } } + return channelID, capKey, 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, +) { + previousChannel, previousChannelFound := k.GetChannel(ctx, portID, channelID) + if !previousChannelFound { + 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", previousChannel.State.String(), "new-state", "TRYOPEN") @@ -233,8 +273,12 @@ func (k Keeper) ChanOpenTry( sdk.NewAttribute(types.AttributeKeyConnectionID, channel.ConnectionHops[0]), ), }) - - return channelID, capKey, nil + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) } // ChanOpenAck is called by the handshake-originating module to acknowledge the @@ -294,17 +338,34 @@ func (k Keeper) ChanOpenAck( return err } - k.Logger(ctx).Info("channel state updated", "port-id", portID, "channel-id", channelID, "previous-state", channel.State.String(), "new-state", "OPEN") + return nil +} - defer func() { - telemetry.IncrCounter(1, "ibc", "channel", "open-ack") - }() +// 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.Sprintf("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", channel.State.String(), "new-state", "OPEN") + + defer func() { + telemetry.IncrCounter(1, "ibc", "channel", "open-ack") + }() + ctx.EventManager().EmitEvents(sdk.Events{ sdk.NewEvent( types.EventTypeChannelOpenAck, @@ -315,8 +376,13 @@ func (k Keeper) ChanOpenAck( sdk.NewAttribute(types.AttributeKeyConnectionID, channel.ConnectionHops[0]), ), }) + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) - return nil } // ChanOpenConfirm is called by the counterparty module to close their end of the @@ -373,6 +439,21 @@ func (k Keeper) ChanOpenConfirm( return err } + return nil +} + +// 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.Sprintf("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", "TRYOPEN", "new-state", "OPEN") @@ -391,8 +472,12 @@ func (k Keeper) ChanOpenConfirm( sdk.NewAttribute(types.AttributeKeyConnectionID, channel.ConnectionHops[0]), ), }) - - return nil + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) } // Closing Handshake diff --git a/modules/core/04-channel/keeper/handshake_test.go b/modules/core/04-channel/keeper/handshake_test.go index 1ed17c6d3fc..a9c24beb0aa 100644 --- a/modules/core/04-channel/keeper/handshake_test.go +++ b/modules/core/04-channel/keeper/handshake_test.go @@ -166,6 +166,19 @@ func (suite *KeeperTestSuite) TestChanOpenTry() { previousChannelID = path.EndpointB.ChannelID portCap = suite.chainB.GetPortCapability(ibctesting.MockPort) }, true}, + {"previous channel with invalid version, crossing hello", func() { + suite.coordinator.SetupConnections(path) + path.SetChannelOrdered() + + // modify channel version + path.EndpointA.ChannelConfig.Version = "invalid version" + + err := suite.coordinator.ChanOpenInitOnBothChains(path) + suite.Require().NoError(err) + + previousChannelID = path.EndpointB.ChannelID + portCap = suite.chainB.GetPortCapability(ibctesting.MockPort) + }, false}, {"previous channel with invalid state", func() { suite.coordinator.SetupConnections(path) @@ -272,7 +285,7 @@ func (suite *KeeperTestSuite) TestChanOpenTry() { channelID, cap, err := suite.chainB.App.GetIBCKeeper().ChannelKeeper.ChanOpenTry( suite.chainB.GetContext(), types.ORDERED, []string{path.EndpointB.ConnectionID}, - path.EndpointB.ChannelConfig.PortID, previousChannelID, portCap, counterparty, path.EndpointB.ChannelConfig.Version, path.EndpointA.ChannelConfig.Version, + path.EndpointB.ChannelConfig.PortID, previousChannelID, portCap, counterparty, path.EndpointA.ChannelConfig.Version, proof, malleateHeight(proofHeight, heightDiff), ) diff --git a/modules/core/04-channel/keeper/keeper.go b/modules/core/04-channel/keeper/keeper.go index e680b23f182..67396423d48 100644 --- a/modules/core/04-channel/keeper/keeper.go +++ b/modules/core/04-channel/keeper/keeper.go @@ -403,6 +403,16 @@ func (k Keeper) GetChannelClientState(ctx sdk.Context, portID, channelID string) return connection.ClientId, clientState, nil } +// GetConnection wraps the conenction keeper's GetConnection function. +func (k Keeper) GetConnection(ctx sdk.Context, connectionID string) (exported.ConnectionI, error) { + connection, found := k.connectionKeeper.GetConnection(ctx, connectionID) + if !found { + return nil, sdkerrors.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, exported.ConnectionI, error) { channel, found := k.GetChannel(ctx, portID, channelID) diff --git a/modules/core/04-channel/types/msgs.go b/modules/core/04-channel/types/msgs.go index d940eb9ae8c..b6e625ff32e 100644 --- a/modules/core/04-channel/types/msgs.go +++ b/modules/core/04-channel/types/msgs.go @@ -61,6 +61,8 @@ func (msg MsgChannelOpenInit) GetSigners() []sdk.AccAddress { var _ sdk.Msg = &MsgChannelOpenTry{} // 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. // nolint:interfacer func NewMsgChannelOpenTry( portID, previousChannelID, version string, channelOrder Order, connectionHops []string, diff --git a/modules/core/04-channel/types/tx.pb.go b/modules/core/04-channel/types/tx.pb.go index 1a007540a64..2d3c75345ce 100644 --- a/modules/core/04-channel/types/tx.pb.go +++ b/modules/core/04-channel/types/tx.pb.go @@ -108,12 +108,14 @@ func (m *MsgChannelOpenInitResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgChannelOpenInitResponse proto.InternalMessageInfo // MsgChannelOpenInit defines a msg sent by a Relayer to try to open a channel -// on Chain B. +// 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" yaml:"port_id"` // in the case of crossing hello's, when both chains call OpenInit, we need // the channel identifier of the previous channel in state INIT - PreviousChannelId string `protobuf:"bytes,2,opt,name=previous_channel_id,json=previousChannelId,proto3" json:"previous_channel_id,omitempty" yaml:"previous_channel_id"` + PreviousChannelId string `protobuf:"bytes,2,opt,name=previous_channel_id,json=previousChannelId,proto3" json:"previous_channel_id,omitempty" yaml:"previous_channel_id"` + // 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" yaml:"counterparty_version"` ProofInit []byte `protobuf:"bytes,5,opt,name=proof_init,json=proofInit,proto3" json:"proof_init,omitempty" yaml:"proof_init"` diff --git a/modules/core/05-port/keeper/grpc_query.go b/modules/core/05-port/keeper/grpc_query.go deleted file mode 100644 index 508c9749ccc..00000000000 --- a/modules/core/05-port/keeper/grpc_query.go +++ /dev/null @@ -1,52 +0,0 @@ -package keeper - -import ( - "context" - - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - "github.com/cosmos/ibc-go/v3/modules/core/05-port/types" - host "github.com/cosmos/ibc-go/v3/modules/core/24-host" -) - -var _ types.QueryServer = (*Keeper)(nil) - -// AppVersion implements the Query/AppVersion gRPC method -func (q Keeper) AppVersion(c context.Context, req *types.QueryAppVersionRequest) (*types.QueryAppVersionResponse, error) { - if req == nil { - return nil, status.Error(codes.InvalidArgument, "empty request") - } - - if err := validategRPCRequest(req.PortId); err != nil { - return nil, err - } - - ctx := sdk.UnwrapSDKContext(c) - module, _, err := q.LookupModuleByPort(ctx, req.PortId) - if err != nil { - return nil, status.Errorf(codes.NotFound, sdkerrors.Wrap(err, "could not retrieve module from port-id").Error()) - } - - ibcModule, found := q.Router.GetRoute(module) - if !found { - return nil, status.Errorf(codes.NotFound, sdkerrors.Wrapf(types.ErrInvalidRoute, "route not found to module: %s", module).Error()) - } - - version, err := ibcModule.NegotiateAppVersion(ctx, req.Ordering, req.ConnectionId, req.PortId, *req.Counterparty, req.ProposedVersion) - if err != nil { - return nil, status.Errorf(codes.InvalidArgument, sdkerrors.Wrap(err, "version negotation failed").Error()) - } - - return types.NewQueryAppVersionResponse(req.PortId, version), nil -} - -func validategRPCRequest(portID string) error { - if err := host.PortIdentifierValidator(portID); err != nil { - return status.Error(codes.InvalidArgument, err.Error()) - } - - return nil -} diff --git a/modules/core/05-port/keeper/grpc_query_test.go b/modules/core/05-port/keeper/grpc_query_test.go deleted file mode 100644 index d7f144ab3bf..00000000000 --- a/modules/core/05-port/keeper/grpc_query_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package keeper_test - -import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - - channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" - "github.com/cosmos/ibc-go/v3/modules/core/05-port/types" - "github.com/cosmos/ibc-go/v3/testing/mock" -) - -func (suite *KeeperTestSuite) TestAppVersion() { - var ( - req *types.QueryAppVersionRequest - expVersion string - ) - - testCases := []struct { - msg string - malleate func() - expPass bool - }{ - { - "empty request", - func() { - req = nil - }, - false, - }, - { - "invalid port ID", - func() { - req = &types.QueryAppVersionRequest{ - PortId: "", - } - }, - false, - }, - { - "module not found", - func() { - req = &types.QueryAppVersionRequest{ - PortId: "mock-port-id", - } - }, - false, - }, - { - "version negotiation failure", - func() { - - expVersion = mock.Version - - req = &types.QueryAppVersionRequest{ - PortId: "mock", // retrieves the mock testing module - Counterparty: &channeltypes.Counterparty{ - PortId: "mock-port-id", - ChannelId: "mock-channel-id", - }, - ProposedVersion: "invalid-proposed-version", - } - }, - false, - }, - { - "success", - func() { - - expVersion = mock.Version - - req = &types.QueryAppVersionRequest{ - PortId: "mock", // retrieves the mock testing module - Counterparty: &channeltypes.Counterparty{ - PortId: "mock-port-id", - ChannelId: "mock-channel-id", - }, - ProposedVersion: mock.Version, - } - }, - true, - }, - } - - for _, tc := range testCases { - suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { - suite.SetupTest() // reset - - tc.malleate() - - ctx := sdk.WrapSDKContext(suite.ctx) - res, err := suite.keeper.AppVersion(ctx, req) - - if tc.expPass { - suite.Require().NoError(err) - suite.Require().NotNil(res) - suite.Require().Equal(expVersion, res.Version) - } else { - suite.Require().Error(err) - } - }) - } -} diff --git a/modules/core/05-port/types/module.go b/modules/core/05-port/types/module.go index cd8c5719dc3..9c7442a9d76 100644 --- a/modules/core/05-port/types/module.go +++ b/modules/core/05-port/types/module.go @@ -2,8 +2,8 @@ package types import ( sdk "github.com/cosmos/cosmos-sdk/types" - capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" "github.com/cosmos/ibc-go/v3/modules/core/exported" ) @@ -11,6 +11,10 @@ import ( // 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. + // OnChanOpenInit should return an error if the provided version is invalid. OnChanOpenInit( ctx sdk.Context, order channeltypes.Order, @@ -22,6 +26,14 @@ type IBCModule interface { version 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, @@ -30,10 +42,11 @@ type IBCModule interface { channelID string, channelCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, - version, counterpartyVersion string, - ) error + ) (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, @@ -41,6 +54,7 @@ type IBCModule interface { counterpartyVersion string, ) error + // OnChanOpenConfirm will perform custom CONFIRM logic and may error to abort the handshake. OnChanOpenConfirm( ctx sdk.Context, portID, @@ -82,18 +96,6 @@ type IBCModule interface { packet channeltypes.Packet, relayer sdk.AccAddress, ) error - - // 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) } // ICS4Wrapper implements the ICS4 interfaces that IBC applications use to send packets and acknolwedgements. diff --git a/modules/core/24-host/doc.go b/modules/core/24-host/doc.go new file mode 100644 index 00000000000..0d73c4e7efe --- /dev/null +++ b/modules/core/24-host/doc.go @@ -0,0 +1,9 @@ +/* +24-host is an implementation of ICS24. + +The storage path supported are defined in [ICS24](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/keeper/grpc_query.go b/modules/core/keeper/grpc_query.go index 5e45facf84f..2fb171a9c02 100644 --- a/modules/core/keeper/grpc_query.go +++ b/modules/core/keeper/grpc_query.go @@ -6,7 +6,6 @@ import ( clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" connectiontypes "github.com/cosmos/ibc-go/v3/modules/core/03-connection/types" channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" - porttypes "github.com/cosmos/ibc-go/v3/modules/core/05-port/types" ) // ClientState implements the IBC QueryServer interface @@ -133,8 +132,3 @@ func (q Keeper) UnreceivedAcks(c context.Context, req *channeltypes.QueryUnrecei func (q Keeper) NextSequenceReceive(c context.Context, req *channeltypes.QueryNextSequenceReceiveRequest) (*channeltypes.QueryNextSequenceReceiveResponse, error) { return q.ChannelKeeper.NextSequenceReceive(c, req) } - -// AppVersion implements the IBC QueryServer interface -func (q Keeper) AppVersion(c context.Context, req *porttypes.QueryAppVersionRequest) (*porttypes.QueryAppVersionResponse, error) { - return q.PortKeeper.AppVersion(c, req) -} diff --git a/modules/core/keeper/msg_server.go b/modules/core/keeper/msg_server.go index e2c92fdeb1d..dbc4ac07812 100644 --- a/modules/core/keeper/msg_server.go +++ b/modules/core/keeper/msg_server.go @@ -255,6 +255,8 @@ func (k Keeper) ConnectionOpenConfirm(goCtx context.Context, msg *connectiontype } // 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 exection. func (k Keeper) ChannelOpenInit(goCtx context.Context, msg *channeltypes.MsgChannelOpenInit) (*channeltypes.MsgChannelOpenInitResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) @@ -264,6 +266,13 @@ func (k Keeper) ChannelOpenInit(goCtx context.Context, msg *channeltypes.MsgChan return nil, sdkerrors.Wrap(err, "could not retrieve module from port-id") } + // Retrieve application callbacks from router + cbs, ok := k.Router.GetRoute(module) + if !ok { + return nil, sdkerrors.Wrapf(porttypes.ErrInvalidRoute, "route not found to module: %s", module) + } + + // Perform 04-channel verification channelID, cap, err := k.ChannelKeeper.ChanOpenInit( ctx, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.PortId, portCap, msg.Channel.Counterparty, msg.Channel.Version, @@ -272,63 +281,58 @@ func (k Keeper) ChannelOpenInit(goCtx context.Context, msg *channeltypes.MsgChan return nil, sdkerrors.Wrap(err, "channel handshake open init failed") } - // Retrieve callbacks from router - cbs, ok := k.Router.GetRoute(module) - if !ok { - return nil, sdkerrors.Wrapf(porttypes.ErrInvalidRoute, "route not found to module: %s", module) - } - + // Perform application logic callback if err = cbs.OnChanOpenInit(ctx, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.PortId, channelID, cap, msg.Channel.Counterparty, msg.Channel.Version); err != nil { return nil, sdkerrors.Wrap(err, "channel open init callback failed") } - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, channeltypes.AttributeValueCategory), - ), - }) + // Write channel into state + k.ChannelKeeper.WriteOpenInitChannel(ctx, msg.PortId, channelID, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.Channel.Counterparty, msg.Channel.Version) return &channeltypes.MsgChannelOpenInitResponse{}, 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 exection. func (k Keeper) ChannelOpenTry(goCtx context.Context, msg *channeltypes.MsgChannelOpenTry) (*channeltypes.MsgChannelOpenTryResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) + // 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 application callbacks from router + cbs, ok := k.Router.GetRoute(module) + if !ok { + return nil, sdkerrors.Wrapf(porttypes.ErrInvalidRoute, "route not found to module: %s", module) + } + + // Perform 04-channel verification channelID, cap, err := k.ChannelKeeper.ChanOpenTry(ctx, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.PortId, msg.PreviousChannelId, - portCap, msg.Channel.Counterparty, msg.Channel.Version, msg.CounterpartyVersion, msg.ProofInit, msg.ProofHeight, + portCap, msg.Channel.Counterparty, msg.CounterpartyVersion, msg.ProofInit, msg.ProofHeight, ) if err != nil { return nil, sdkerrors.Wrap(err, "channel handshake open try failed") } - // Retrieve callbacks from router - cbs, ok := k.Router.GetRoute(module) - if !ok { - return nil, sdkerrors.Wrapf(porttypes.ErrInvalidRoute, "route not found to module: %s", module) - } - - if err = cbs.OnChanOpenTry(ctx, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.PortId, channelID, cap, msg.Channel.Counterparty, msg.Channel.Version, msg.CounterpartyVersion); err != nil { + // Perform application logic callback + version, err := cbs.OnChanOpenTry(ctx, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.PortId, channelID, cap, msg.Channel.Counterparty, msg.CounterpartyVersion) + if err != nil { return nil, sdkerrors.Wrap(err, "channel open try callback failed") } - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, channeltypes.AttributeValueCategory), - ), - }) + // Write channel into state + k.ChannelKeeper.WriteOpenTryChannel(ctx, msg.PortId, channelID, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.Channel.Counterparty, version) return &channeltypes.MsgChannelOpenTryResponse{}, 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 exection. func (k Keeper) ChannelOpenAck(goCtx context.Context, msg *channeltypes.MsgChannelOpenAck) (*channeltypes.MsgChannelOpenAckResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) @@ -338,34 +342,33 @@ func (k Keeper) ChannelOpenAck(goCtx context.Context, msg *channeltypes.MsgChann return nil, sdkerrors.Wrap(err, "could not retrieve module from port-id") } - // Retrieve callbacks from router + // Retrieve application callbacks from router cbs, ok := k.Router.GetRoute(module) if !ok { return nil, sdkerrors.Wrapf(porttypes.ErrInvalidRoute, "route not found to module: %s", module) } - err = k.ChannelKeeper.ChanOpenAck( + // Perform 04-channel verification + if err = k.ChannelKeeper.ChanOpenAck( ctx, msg.PortId, msg.ChannelId, cap, msg.CounterpartyVersion, msg.CounterpartyChannelId, msg.ProofTry, msg.ProofHeight, - ) - if err != nil { + ); err != nil { return nil, sdkerrors.Wrap(err, "channel handshake open ack failed") } + // Perform application logic callback if err = cbs.OnChanOpenAck(ctx, msg.PortId, msg.ChannelId, msg.CounterpartyVersion); err != nil { return nil, sdkerrors.Wrap(err, "channel open ack callback failed") } - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, channeltypes.AttributeValueCategory), - ), - }) + // Write channel into state + k.ChannelKeeper.WriteOpenAckChannel(ctx, msg.PortId, msg.ChannelId, msg.CounterpartyVersion, msg.CounterpartyChannelId) 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 exection. func (k Keeper) ChannelOpenConfirm(goCtx context.Context, msg *channeltypes.MsgChannelOpenConfirm) (*channeltypes.MsgChannelOpenConfirmResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) @@ -375,27 +378,24 @@ func (k Keeper) ChannelOpenConfirm(goCtx context.Context, msg *channeltypes.MsgC return nil, sdkerrors.Wrap(err, "could not retrieve module from port-id") } - // Retrieve callbacks from router + // Retrieve application callbacks from router cbs, ok := k.Router.GetRoute(module) if !ok { return nil, sdkerrors.Wrapf(porttypes.ErrInvalidRoute, "route not found to module: %s", module) } - err = k.ChannelKeeper.ChanOpenConfirm(ctx, msg.PortId, msg.ChannelId, cap, msg.ProofAck, msg.ProofHeight) - if err != nil { + // Perform 04-channel verification + if err = k.ChannelKeeper.ChanOpenConfirm(ctx, msg.PortId, msg.ChannelId, cap, msg.ProofAck, msg.ProofHeight); err != nil { return nil, sdkerrors.Wrap(err, "channel handshake open confirm failed") } + // Perform application logic callback if err = cbs.OnChanOpenConfirm(ctx, msg.PortId, msg.ChannelId); err != nil { return nil, sdkerrors.Wrap(err, "channel open confirm callback failed") } - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, channeltypes.AttributeValueCategory), - ), - }) + // Write channel into state + k.ChannelKeeper.WriteOpenConfirmChannel(ctx, msg.PortId, msg.ChannelId) return &channeltypes.MsgChannelOpenConfirmResponse{}, nil } diff --git a/modules/core/spec/01_concepts.md b/modules/core/spec/01_concepts.md index 05c4e8a3104..0ab6fa70710 100644 --- a/modules/core/spec/01_concepts.md +++ b/modules/core/spec/01_concepts.md @@ -4,28 +4,6 @@ order: 1 # Concepts -## Client Creation, Updates, and Upgrades - -IBC clients are on chain light clients. The light client is responsible for verifying -counterparty state. A light client can be created by any user submitting a valid initial -`ClientState` and `ConsensusState`. The client identifier is auto generated using the -client type and the global client counter appended in the format: `{client-type}-{N}`. -Clients are given a client identifier prefixed store to store their associated client -state and consensus states. Consensus states are stored using their associated height. - -Clients can be updated by any user submitting a valid `Header`. The client state callback -to `CheckHeaderAndUpdateState` is responsible for verifying the header against previously -stored state. The function should also return the updated client state and consensus state -if the header is considered a valid update. A light client, such as Tendermint, may have -client specific parameters like `TrustLevel` which must be considered valid in relation -to the `Header`. The update height is not necessarily the lastest height of the light -client. Updates may fill in missing consensus state heights. - -Clients may be upgraded. The upgrade should be verified using `VerifyUpgrade`. It is not -a requirement to allow for light client upgrades. For example, the solo machine client -will simply return an error on `VerifyUpgrade`. Clients which implement upgrades -are expected to account for, but not necessarily support, planned and unplanned upgrades. - ## Client Misbehaviour IBC clients must freeze when the counterparty chain becomes byzantine and @@ -48,93 +26,6 @@ Governance may then choose to override a frozen client and provide the correct, canonical Header so that the client can continue operating after the Misbehaviour submission. -## ClientUpdateProposal - -A governance proposal may be passed to update a specified client using another client -known as the "substitute client". This is useful in unfreezing clients or updating -expired clients, thereby making the effected channels active again. Each client is -expected to implement this functionality. A client may choose to disallow an update -by a governance proposal by returning an error in the client state function 'CheckSubstituteAndUpdateState'. - -The localhost client cannot be updated by a governance proposal. - -The solo machine client requires the boolean flag 'AllowUpdateAfterProposal' to be set -to true in order to be updated by a proposal. This is set upon client creation and cannot -be updated later. - -The tendermint client has two flags update flags, 'AllowUpdateAfterExpiry' and -'AllowUpdateAfterMisbehaviour'. The former flag can only be used to unexpire clients. The -latter flag can be used to unfreeze a client and if necessary it will also unexpire the client. -It is best practice to initialize a new substitute client instead of using an existing one -This avoids potential issues of the substitute becoming frozen due to misbehaviour or the -subject client becoming refrozen due to misbehaviour not being expired at the time the -proposal passes. These boolean flags are set upon client creation and cannot be updated later. - -The `CheckSubstituteAndUpdateState` function provides the light client with its own client -store, the client store of the substitute, the substitute client state, and the intitial -height that should be used when referring to the substitute client. Most light client -implementations should copy consensus states from the substitute to the subject, but -are not required to do so. Light clients may copy informationa as they deem necessary. - -It is not recommended to use a substitute client in normal operations since the subject -light client will be given unrestricted access to the substitute client store. Governance -should not pass votes which enable byzantine light client modules from modifying the state -of the substitute. - -## 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. -An 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`). - -`Heights` that share the same revision number can be compared by simply comparing their respective `RevisionHeights`. -Heights that do not share the same revision number will only be compared using their respective `RevisionNumbers`. -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 chain-id 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 chain-ids -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 may 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 should use the concrete implementation provided in -`02-client/types` and reproduced above. - ## Connection Handshake The connection handshake occurs in 4 steps as defined in [ICS 03](https://github.com/cosmos/ibc/blob/master/spec/core/ics-003-connection-semantics). @@ -214,40 +105,6 @@ with regards to version selection in `ConnOpenTry`. Each version in a set of versions should have a unique version identifier. ::: -## Channel Handshake - -The channel handshake occurs in 4 steps as defined in [ICS 04](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics). - -`ChanOpenInit` is the first attempt to initialize a channel on top of an existing connection. -The handshake is expected to succeed if the version selected for the existing connection is a -supported IBC version. The portID must correspond to a port already binded upon `InitChain`. -The channel identifier for the counterparty channel must be left empty indicating that the -counterparty must select its own identifier. The channel identifier is auto derived in the -format: `channel{N}` where N is the next sequence to be used. The channel is set and stored -in the INIT state upon success. The channel parameters `NextSequenceSend`, `NextSequenceRecv`, -and `NextSequenceAck` are all set to 1 and a channel capability is created for the given -portID and channelID path. - -`ChanOpenTry` is a response to a chain executing `ChanOpenInit`. If the executing chain is calling -`ChanOpenTry` after previously executing `ChanOpenInit` then the provided channel parameters must -match the previously selected parameters. If the previous channel does not exist then a channel -identifier is generated in the same format as done in `ChanOpenInit`. The connection the channel -is created on top of must be an OPEN state and its IBC version must support the desired channel -type being created (ORDERED, UNORDERED, etc). The executing chain will verify that the channel -state of the counterparty is in INIT. The executing chain will set and store the channel state -in TRYOPEN. The channel parameters `NextSequenceSend`, `NextSequenceRecv`, and `NextSequenceAck` -are all set to 1 and a channel capability is created for the given portID and channelID path only -if the channel did not previously exist. - -`ChanOpenAck` may be called on a chain when the counterparty channel has entered TRYOPEN. A -previous channel on the executing chain must exist be in either INIT or TRYOPEN state. If the -counterparty selected its own channel identifier, it will be validated in the basic validation -of `MsgChanOpenAck`. The executing chain verifies that the counterparty channel state is in -TRYOPEN. The channel is set and stored in the OPEN state upon success. - -`ChanOpenConfirm` is a response to a chain executing `ChanOpenAck`. The executing chain's -previous channel state must be in TRYOPEN. The executing chain verifies that the counterparty -channel state is OPEN. The channel is set and stored in the OPEN state upon success. ## Channel Version Negotiation @@ -346,14 +203,6 @@ commitments could be removed from channels which do not write packet acknowledgements and acknowledgements could be removed when a packet has completed its life cycle. -## Timing out Packets - -A packet may be timed out on the receiving chain if the packet timeout height or timestamp has -been surpassed on the receving chain or the channel has closed. A timed out -packet can only occur if the packet has never been received on the receiving -chain. ORDERED channels will verify that the packet sequence is greater than -the `NextSequenceRecv` on the receiving chain. UNORDERED channels will verify -that the packet receipt has not been written on the receiving chain. A timeout on channel closure will additionally verify that the counterparty channel has been closed. A successful timeout may execute application logic as appropriate. @@ -362,41 +211,4 @@ surpassed on the receiving chain for a timeout to be valid. A timeout timestamp or timeout height with a 0 value indicates the timeout field may be ignored. Each packet is required to have at least one valid timeout field. -## Closing Channels - -Closing a channel occurs in 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` will close 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 will be closed 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. - -## Port and Channel Capabilities - -## Hostname Validation - -Hostname validation is implemented as defined in [ICS 24](https://github.com/cosmos/ibc/tree/master/spec/core/ics-024-host-requirements). - -The 24-host sub-module parses and validates identifiers. It also builds -the key paths used to store IBC related information. - -A valid identifier must conatin only alphanumeric characters or the -following list of allowed characters: -".", "\_", "+", "-", "#", "[", "]", "<", ">" - -- Client identifiers must contain between 9 and 64 characters. -- Connection identifiers must contain between 10 and 64 characters. -- Channel identifiers must contain between 10 and 64 characters. -- Port identifiers must contain between 2 and 64 characters. - -## Proofs -Proofs for counterparty state validation are provided as bytes. These bytes -can be unmarshaled into proto definitions as necessary by light clients. -For example, the Tendermint light client will use the bytes as a merkle -proof where as the solo machine client will unmarshal the proof into -several layers proto definitions used for signature verficiation. diff --git a/modules/core/spec/02_state.md b/modules/core/spec/02_state.md deleted file mode 100644 index 8357f923c42..00000000000 --- a/modules/core/spec/02_state.md +++ /dev/null @@ -1,28 +0,0 @@ - - -# State - -The paths for the values stored in state is defined [here](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements#path-space). -Additionally, the SDK adds a prefix to the path to be able to aggregate the values for querying purposes. -The client type is not stored since it can be obtained through the client state. - -| Prefix | Path | Value type | -|--------|-----------------------------------------------------------------------------|----------------| -| "0/" | "clients/{identifier}/clientState" | ClientState | -| "0/" | "clients/{identifier}/consensusStates/{height}" | ConsensusState | -| "0/" | "clients/{identifier}/connections" | []string | -| "0/" | "nextClientSequence | uint64 | -| "0/" | "connections/{identifier}" | ConnectionEnd | -| "0/" | "nextConnectionSequence" | uint64 | -| "0/" | "ports/{identifier}" | CapabilityKey | -| "0/" | "channelEnds/ports/{identifier}/channels/{identifier}" | ChannelEnd | -| "0/" | "nextChannelSequence" | uint64 | -| "0/" | "capabilities/ports/{identifier}/channels/{identifier}" | CapabilityKey | -| "0/" | "nextSequenceSend/ports/{identifier}/channels/{identifier}" | uint64 | -| "0/" | "nextSequenceRecv/ports/{identifier}/channels/{identifier}" | uint64 | -| "0/" | "nextSequenceAck/ports/{identifier}/channels/{identifier}" | uint64 | -| "0/" | "commitments/ports/{identifier}/channels/{identifier}/sequences/{sequence}" | bytes | -| "0/" | "receipts/ports/{identifier}/channels/{identifier}/sequences/{sequence}" | bytes | -| "0/" | "acks/ports/{identifier}/channels/{identifier}/sequences/{sequence}" | bytes | diff --git a/modules/core/spec/03_state_transitions.md b/modules/core/spec/03_state_transitions.md deleted file mode 100644 index 518ff9247b9..00000000000 --- a/modules/core/spec/03_state_transitions.md +++ /dev/null @@ -1,106 +0,0 @@ - - -# State Transitions - -The described state transitions assume successful message exection. - -## Create Client - -`MsgCreateClient` will initialize and store a `ClientState` and `ConsensusState` in the sub-store -created using a generated client identifier. - -## Update Client - -`MsgUpdateClient` will update the `ClientState` and create a new `ConsensusState` for the -update height. - -## Misbehaviour - -`MsgSubmitMisbehaviour` will freeze a client. - -## Upgrade Client - -`MsgUpgradeClient` will upgrade the `ClientState` and `ConsensusState` to the update chain level -parameters and if applicable will update to the new light client implementation. - -## Client Update Proposal - -An Update Client Proposal will unfreeze a client (if necessary) and set an updated `ClientState`. -The light client may make optional modifications to the client prefixed store of the subject client -including copying `ConsensusStates` from the substitute to the subject. - -## Connection Open Init - -`MsgConnectionOpenInit` will initialize a connection state in INIT. - -## Connection Open Try - -`MsgConnectionOpenTry` will initialize or update a connection state to be in TRYOPEN. - -## Connection Open Ack - -`MsgConnectionOpenAck` will update a connection state from INIT or TRYOPEN to be in OPEN. - -## Connection Open Confirm - -`MsgConnectionOpenAck` will update a connection state from TRYOPEN to OPEN. - -## Channel Open Init - -`MsgChannelOpenInit` will initialize a channel state in INIT. It will create a channel capability -and set all Send, Receive and Ack Sequences to 1 for the channel. - -## Channel Open Try - -`MsgChannelOpenTry` will initialize or update a channel state to be in TRYOPEN. If the channel -is being initialized, It will create a channel capability and set all Send, Receive and Ack -Sequences to 1 for the channel. - -## Channel Open Ack - -`MsgChannelOpenAck` will update the channel state to OPEN. It will set the version and channel -identifier for its counterparty. - -## Channel Open Confirm - -`MsgChannelOpenConfirm` will update the channel state to OPEN. - -## Channel Close Init - -`MsgChannelCloseInit` will update the channel state to CLOSED. - -## Channel Close Confirm - -`MsgChannelCloseConfirm` will update the channel state to CLOSED. - -## Send Packet - -A application calling `ChannelKeeper.SendPacket` will incremenet the next sequence send and set -a hash of the packet as the packet commitment. - -## Receive Packet - -`MsgRecvPacket` will increment the next sequence receive for ORDERED channels and set a packet -receipt for UNORDERED channels. - -## Write Acknowledgement - -`WriteAcknowledgement` may be executed synchronously during the execution of `MsgRecvPacket` or -asynchonously by an application module. It writes an acknowledgement to the store. - -## Acknowledge Packet - -`MsgAcknowledgePacket` deletes the packet commitment and for ORDERED channels increments next -sequences ack. - -## Timeout Packet - -`MsgTimeoutPacket` deletes the packet commitment and for ORDERED channels sets the channel state -to CLOSED. - -## Timeout Packet on Channel Closure - -`MsgTimeoutOnClose` deletes the packet commitment and for ORDERED channels sets the channel state -to CLOSED. diff --git a/modules/core/spec/04_messages.md b/modules/core/spec/04_messages.md deleted file mode 100644 index 3728e6d6f32..00000000000 --- a/modules/core/spec/04_messages.md +++ /dev/null @@ -1,497 +0,0 @@ - - -# Messages - -In this section we describe the processing of the IBC messages and the corresponding updates to the state. - -## ICS 02 - Client - -### MsgCreateClient - -A light client is created using the `MsgCreateClient`. - -```go -type MsgCreateClient struct { - ClientState *types.Any // proto-packed client state - ConsensusState *types.Any // proto-packed consensus state - Signer sdk.AccAddress -} -``` - -This message is expected to fail if: - -- `ClientState` is empty or invalid -- `ConsensusState` is empty or invalid -- `Signer` is empty - -The message creates and stores a light client with an initial consensus state using a generated client -identifier. - -### MsgUpdateClient - -A light client is updated with a new header using the `MsgUpdateClient`. - -```go -type MsgUpdateClient struct { - ClientId string - Header *types.Any // proto-packed header - Signer sdk.AccAddress -} -``` - -This message is expected to fail if: - -- `ClientId` is invalid (not alphanumeric or not within 10-20 characters) -- `Header` is empty or invalid -- `Signer` is empty -- A `ClientState` hasn't been created for the given ID -- The client is frozen due to misbehaviour and cannot be updated -- The header fails to provide a valid update for the client - -The message validates the header and updates the client state and consensus state for the -header height. - -### MsgUpgradeClient -```go -type MsgUpgradeClient struct { - ClientId string - ClientState *types.Any // proto-packed client state - UpgradeHeight *Height - ProofUpgrade []byte - Signer string -} -``` - -This message is expected to fail if: - -- `ClientId` is invalid (not alphanumeric or not within 10-20 characters) -- `ClientState` is empty or invalid -- `UpgradeHeight` is empty or zero -- `ProofUpgrade` is empty -- `Signer` is empty -- A `ClientState` hasn't been created for the given ID -- The client is frozen due to misbehaviour and cannot be upgraded -- The upgrade proof fails - -The message upgrades the client state and consensus state upon successful validation of a -chain upgrade. - -### MsgSubmitMisbehaviour - -Submit a evidence of light client misbehaviour to freeze the client state and prevent additional packets from being relayed. - -```go -type MsgSubmitMisbehaviour struct { - ClientId string - Misbehaviour *types.Any // proto-packed misbehaviour - Signer sdk.AccAddress -} -``` - -This message is expected to fail if: - -- `ClientId` is invalid (not alphanumeric or not within 10-20 characters) -- `Misbehaviour` is empty or invalid -- `Signer` is empty -- A `ClientState` hasn't been created for the given ID -- `Misbehaviour` check failed - -The message verifies the misbehaviour and freezes the client. - -## ICS 03 - Connection - -### MsgConnectionOpenInit - -A connection is initialized on a light client using the `MsgConnectionOpenInit`. - -```go -type MsgConnectionOpenInit struct { - ClientId string - Counterparty Counterparty - Version string - Signer sdk.AccAddress -} -``` - -This message is expected to fail if: -- `ClientId` is invalid (see naming requirements) -- `Counterparty` is empty -- 'Version' is not empty and invalid -- `Signer` is empty -- A Client hasn't been created for the given ID -- A Connection for the given ID already exists - -The message creates a connection for the given ID with an INIT state. - -### MsgConnectionOpenTry - -When a counterparty connection is initialized then a connection is initialized on a light client -using the `MsgConnectionOpenTry`. - -```go -type MsgConnectionOpenTry struct { - ClientId string - PreviousConnectionId string - ClientState *types.Any // proto-packed counterparty client - Counterparty Counterparty - CounterpartyVersions []string - ProofHeight Height - ProofInit []byte - ProofClient []byte - ProofConsensus []byte - ConsensusHeight Height - Signer sdk.AccAddress -} -``` - -This message is expected to fail if: - -- `ClientId` is invalid (see naming requirements) -- `PreviousConnectionId` is not empty and invalid (see naming requirements) -- `ClientState` is not a valid client of the executing chain -- `Counterparty` is empty -- `CounterpartyVersions` is empty -- `ProofHeight` is zero -- `ProofInit` is empty -- `ProofClient` is empty -- `ProofConsensus` is empty -- `ConsensusHeight` is zero -- `Signer` is empty -- A Client hasn't been created for the given ID -- If a previous connection exists but does not match the supplied parameters. -- `ProofInit` does not prove that the counterparty connection is in state INIT -- `ProofClient` does not prove that the counterparty has stored the `ClientState` provided in message -- `ProofConsensus` does not prove that the counterparty has the correct consensus state for this chain - -The message creates a connection for a generated connection ID with an TRYOPEN State. If a previous -connection already exists, it updates the connection state from INIT to TRYOPEN. - -### MsgConnectionOpenAck - -When a counterparty connection is initialized then a connection is opened on a light client -using the `MsgConnectionOpenAck`. - -```go -type MsgConnectionOpenAck struct { - ConnectionId string - CounterpartyConnectionId string - Version string - ClientState *types.Any // proto-packed counterparty client - ProofHeight Height - ProofTry []byte - ProofClient []byte - ProofConsensus []byte - ConsensusHeight Height - Signer sdk.AccAddress -} -``` - -This message is expected to fail if: - -- `ConnectionId` is invalid (see naming requirements) -- `CounterpartyConnectionId` is invalid (see naming requirements) -- `Version` is empty -- `ClientState` is not a valid client of the executing chain -- `ProofHeight` is zero -- `ProofTry` is empty -- `ProofClient` is empty -- `ProofConsensus` is empty -- `ConsensusHeight` is zero -- `Signer` is empty -- `ProofTry` does not prove that the counterparty connection is in state TRYOPEN -- `ProofClient` does not prove that the counterparty has stored the `ClientState` provided by message -- `ProofConsensus` does not prove that the counterparty has the correct consensus state for this chain - -The message sets the connection state for the given ID to OPEN. `CounterpartyConnectionId` -should be the `ConnectionId` used by the counterparty connection. - -### MsgConnectionOpenConfirm - -When a counterparty connection is opened then a connection is opened on a light client using -the `MsgConnectionOpenConfirm`. - -```go -type MsgConnectionOpenConfirm struct { - ConnectionId string - ProofAck []byte - ProofHeight Height - Signer sdk.AccAddress -} -``` - -This message is expected to fail if: - -- `ConnectionId` is invalid (see naming requirements) -- `ProofAck` is empty -- `ProofHeight` is zero -- `Signer` is empty -- A Connection with the given ID does not exist -- `ProofAck` does not prove that the counterparty connection is in state OPEN - -The message sets the connection state for the given ID to OPEN. - -## ICS 04 - Channels - -### MsgChannelOpenInit - -A channel handshake is initiated by a chain A using the `MsgChannelOpenInit` -message. - -```go -type MsgChannelOpenInit struct { - PortId string - Channel Channel - Signer sdk.AccAddress -} -``` - -This message is expected to fail if: - -- `PortId` is invalid (see naming requirements) -- `Channel` is empty -- `Signer` is empty -- A Channel End exists for the given Channel ID and Port ID - -The message creates a channel on chain A with an INIT state for a generated Channel ID -and Port ID. - -### MsgChannelOpenTry - -A channel handshake initialization attempt is acknowledged by a chain B using -the `MsgChannelOpenTry` message. - -```go -type MsgChannelOpenTry struct { - PortId string - PreviousChannelId string - Channel Channel - CounterpartyVersion string - ProofInit []byte - ProofHeight Height - Signer sdk.AccAddress -} -``` - -This message is expected to fail if: - -- `PortId` is invalid (see naming requirements) -- `PreviousChannelId` is not empty and invalid (see naming requirements) -- `Channel` is empty -- `CounterpartyVersion` is empty -- `ProofInit` is empty -- `ProofHeight` is zero -- `Signer` is empty -- A previous channel exists and does not match the provided parameters. -- `ProofInit` does not prove that the counterparty's Channel state is in INIT - -The message creates a channel on chain B with an TRYOPEN state for using a generated Channel ID -and given Port ID if the previous channel does not already exist. Otherwise it udates the -previous channel state from INIT to TRYOPEN. - - -### MsgChannelOpenAck - -A channel handshake is opened by a chain A using the `MsgChannelOpenAck` message. - -```go -type MsgChannelOpenAck struct { - PortId string - ChannelId string - CounterpartyChannelId string - CounterpartyVersion string - ProofTry []byte - ProofHeight Height - Signer sdk.AccAddress -} -``` - -This message is expected to fail if: - -- `PortId` is invalid (see naming requirements) -- `ChannelId` is invalid (see naming requirements) -- `CounterpartyChannelId` is invalid (see naming requirements) -- `CounterpartyVersion` is empty -- `ProofTry` is empty -- `ProofHeight` is zero -- `Signer` is empty -- `ProofTry` does not prove that the counterparty's Channel state is in TRYOPEN - -The message sets a channel on chain A to state OPEN for the given Channel ID and Port ID. -`CounterpartyChannelId` should be the `ChannelId` used by the counterparty channel. - -### MsgChannelOpenConfirm - -A channel handshake is confirmed and opened by a chain B using the `MsgChannelOpenConfirm` -message. - -```go -type MsgChannelOpenConfirm struct { - PortId string - ChannelId string - ProofAck []byte - ProofHeight Height - Signer sdk.AccAddress -} -``` - -This message is expected to fail if: - -- `PortId` is invalid (see naming requirements) -- `ChannelId` is invalid (see naming requirements) -- `ProofAck` is empty -- `ProofHeight` is zero -- `Signer` is empty -- `ProofAck` does not prove that the counterparty's Channel state is in OPEN - -The message sets a channel on chain B to state OPEN for the given Channel ID and Port ID. - -### MsgChannelCloseInit - -A channel is closed on chain A using the `MsgChannelCloseInit`. - -```go -type MsgChannelCloseInit struct { - PortId string - ChannelId string - Signer sdk.AccAddress -} -``` - -This message is expected to fail if: - -- `PortId` is invalid (see naming requirements) -- `ChannelId` is invalid (see naming requirements) -- `Signer` is empty -- A Channel for the given Port ID and Channel ID does not exist or is already closed - -The message closes a channel on chain A for the given Port ID and Channel ID. - -### MsgChannelCloseConfirm - -A channel is closed on chain B using the `MsgChannelCloseConfirm`. - -```go -type MsgChannelCloseConfirm struct { - PortId string - ChannelId string - ProofInit []byte - ProofHeight Height - Signer sdk.AccAddress -} -``` - -This message is expected to fail if: - -- `PortId` is invalid (see naming requirements) -- `ChannelId` is invalid (see naming requirements) -- `ProofInit` is empty -- `ProofHeight` is zero -- `Signer` is empty -- A Channel for the given Port ID and Channel ID does not exist or is already closed -- `ProofInit` does not prove that the counterparty set its channel to state CLOSED - -The message closes a channel on chain B for the given Port ID and Channel ID. - -### MsgRecvPacket - -A packet is received on chain B using the `MsgRecvPacket`. - -```go -type MsgRecvPacket struct { - Packet Packet - Proof []byte - ProofHeight Height - Signer sdk.AccAddress -} -``` - -This message is expected to fail if: - -- `Proof` is empty -- `ProofHeight` is zero -- `Signer` is empty -- `Packet` fails basic validation -- `Proof` does not prove that the counterparty sent the `Packet`. - -The message receives a packet on chain B. - -### MsgTimeout - -A packet is timed out on chain A using the `MsgTimeout`. - -```go -type MsgTimeout struct { - Packet Packet - Proof []byte - ProofHeight Height - NextSequenceRecv uint64 - Signer sdk.AccAddress -} -``` - -This message is expected to fail if: - -- `Proof` is empty -- `ProofHeight` is zero -- `NextSequenceRecv` is zero -- `Signer` is empty -- `Packet` fails basic validation -- `Proof` does not prove that the packet has not been received on the counterparty chain. - -The message times out a packet that was sent on chain A and never received on chain B. - -### MsgTimeoutOnClose - -A packet is timed out on chain A due to the closure of the channel end on chain B using -the `MsgTimeoutOnClose`. - -```go -type MsgTimeoutOnClose struct { - Packet Packet - Proof []byte - ProofClose []byte - ProofHeight Height - NextSequenceRecv uint64 - Signer sdk.AccAddress -} -``` - -This message is expected to fail if: - -- `Proof` is empty -- `ProofClose` is empty -- `ProofHeight` is zero -- `NextSequenceRecv` is zero -- `Signer` is empty -- `Packet` fails basic validation -- `Proof` does not prove that the packet has not been received on the counterparty chain. -- `ProofClose` does not prove that the counterparty channel end has been closed. - -The message times out a packet that was sent on chain A and never received on chain B. - -### MsgAcknowledgement - -A packet is acknowledged on chain A using the `MsgAcknowledgement`. - -```go -type MsgAcknowledgement struct { - Packet Packet - Acknowledgement []byte - Proof []byte - ProofHeight Height - Signer sdk.AccAddress -} -``` - -This message is expected to fail if: - -- `Proof` is empty -- `ProofHeight` is zero -- `Signer` is empty -- `Packet` fails basic validation -- `Acknowledgement` is empty -- `Proof` does not prove that the counterparty received the `Packet`. - -The message acknowledges that the packet sent from chainA was received on chain B. diff --git a/modules/core/spec/05_callbacks.md b/modules/core/spec/05_callbacks.md deleted file mode 100644 index 0720b7c6232..00000000000 --- a/modules/core/spec/05_callbacks.md +++ /dev/null @@ -1,80 +0,0 @@ - - -# Callbacks - -Application modules implementing the IBC module must implement the following callbacks as found in [05-port](../05-port/types/module.go). -More information on how to implement these callbacks can be found in the [implementation guide](../../../docs/ibc/apps.md). - -```go -// IBCModule defines an interface that implements all the callbacks -// that modules must define as specified in ICS-26 -type IBCModule interface { - OnChanOpenInit( - ctx sdk.Context, - order channeltypes.Order, - connectionHops []string, - portId string, - channelId string, - channelCap *capability.Capability, - counterparty channeltypes.Counterparty, - version string, - ) error - - OnChanOpenTry( - ctx sdk.Context, - order channeltypes.Order, - connectionHops []string, - portId, - channelId string, - channelCap *capability.Capability, - counterparty channeltypes.Counterparty, - version, - counterpartyVersion string, - ) error - - OnChanOpenAck( - ctx sdk.Context, - portId, - channelId string, - counterpartyVersion string, - ) error - - 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 the acknowledgement bytes - // In the case of an asynchronous acknowledgement, nil should be returned. - OnRecvPacket( - ctx sdk.Context, - packet channeltypes.Packet, - ) (*sdk.Result, []byte, error) - - OnAcknowledgementPacket( - ctx sdk.Context, - packet channeltypes.Packet, - acknowledgement []byte, - ) (*sdk.Result, error) - - OnTimeoutPacket( - ctx sdk.Context, - packet channeltypes.Packet, - ) (*sdk.Result, error) -} -``` diff --git a/modules/core/spec/README.md b/modules/core/spec/README.md deleted file mode 100644 index f6de9749b5e..00000000000 --- a/modules/core/spec/README.md +++ /dev/null @@ -1,26 +0,0 @@ - - -# `ibc core` - -## Abstract - -This paper defines the implementation of the IBC protocol on the Cosmos SDK, the -changes made to the specification and where to find each specific ICS spec within -the module. - -For the general specification please refer to the [Interchain Standards](https://github.com/cosmos/ics). - -## Contents - -1. **[Concepts](01_concepts.md)** -2. **[State](02_state.md)** -3. **[State Transitions](03_state_transitions.md)** -4. **[Messages](04_messages.md)** -5. **[Callbacks](05_callbacks.md)** -6. **[Events](06_events.md)** -7. **[Params](07_params.md)** diff --git a/modules/core/types/query.go b/modules/core/types/query.go index dd14f6c57f0..ef9d0589448 100644 --- a/modules/core/types/query.go +++ b/modules/core/types/query.go @@ -9,8 +9,6 @@ import ( connectiontypes "github.com/cosmos/ibc-go/v3/modules/core/03-connection/types" channel "github.com/cosmos/ibc-go/v3/modules/core/04-channel" channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" - port "github.com/cosmos/ibc-go/v3/modules/core/05-port" - porttypes "github.com/cosmos/ibc-go/v3/modules/core/05-port/types" ) // QueryServer defines the IBC interfaces that the gRPC query server must implement @@ -18,7 +16,6 @@ type QueryServer interface { clienttypes.QueryServer connectiontypes.QueryServer channeltypes.QueryServer - porttypes.QueryServer } // RegisterQueryService registers each individual IBC submodule query service @@ -26,5 +23,4 @@ func RegisterQueryService(server grpc.Server, queryService QueryServer) { client.RegisterQueryService(server, queryService) connection.RegisterQueryService(server, queryService) channel.RegisterQueryService(server, queryService) - port.RegisterQueryService(server, queryService) } diff --git a/modules/light-clients/06-solomachine/spec/02_state.md b/modules/light-clients/06-solomachine/spec/02_state.md index a9ff4ea5b47..51cb1f058c6 100644 --- a/modules/light-clients/06-solomachine/spec/02_state.md +++ b/modules/light-clients/06-solomachine/spec/02_state.md @@ -7,6 +7,3 @@ order: 2 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. -These values can be found under the light client paths defined in the IBC -[core store specs](../../../core/spec/02_state.md). - diff --git a/modules/light-clients/06-solomachine/spec/04_messages.md b/modules/light-clients/06-solomachine/spec/04_messages.md deleted file mode 100644 index 465ea6229a7..00000000000 --- a/modules/light-clients/06-solomachine/spec/04_messages.md +++ /dev/null @@ -1,8 +0,0 @@ - - -# Messages - -The messages used to initialize a solo machine light client are defined in the -core sub-module [02-client](../../../core/spec/04_messages.md). diff --git a/modules/light-clients/06-solomachine/spec/README.md b/modules/light-clients/06-solomachine/spec/README.md index 45ad34c3a0e..0879f1bd2be 100644 --- a/modules/light-clients/06-solomachine/spec/README.md +++ b/modules/light-clients/06-solomachine/spec/README.md @@ -23,4 +23,3 @@ diversifier, and timestamp. 1. **[Concepts](01_concepts.md)** 2. **[State](02_state.md)** 3. **[State Transitions](03_state_transitions.md)** -4. **[Messages](04_messages.md)** diff --git a/proto/ibc/core/channel/v1/tx.proto b/proto/ibc/core/channel/v1/tx.proto index c9322c1def7..4f28418c9a2 100644 --- a/proto/ibc/core/channel/v1/tx.proto +++ b/proto/ibc/core/channel/v1/tx.proto @@ -57,7 +57,8 @@ message MsgChannelOpenInit { message MsgChannelOpenInitResponse {} // MsgChannelOpenInit defines a msg sent by a Relayer to try to open a channel -// on Chain B. +// on Chain B. The version field within the Channel field has been deprecated. Its +// value will be ignored by core IBC. message MsgChannelOpenTry { option (gogoproto.equal) = false; option (gogoproto.goproto_getters) = false; @@ -65,7 +66,8 @@ message MsgChannelOpenTry { string port_id = 1 [(gogoproto.moretags) = "yaml:\"port_id\""]; // in the case of crossing hello's, when both chains call OpenInit, we need // the channel identifier of the previous channel in state INIT - string previous_channel_id = 2 [(gogoproto.moretags) = "yaml:\"previous_channel_id\""]; + string previous_channel_id = 2 [(gogoproto.moretags) = "yaml:\"previous_channel_id\""]; + // 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 [(gogoproto.moretags) = "yaml:\"counterparty_version\""]; bytes proof_init = 5 [(gogoproto.moretags) = "yaml:\"proof_init\""]; diff --git a/proto/ibc/core/port/v1/query.proto b/proto/ibc/core/port/v1/query.proto deleted file mode 100644 index 4ae44a5db70..00000000000 --- a/proto/ibc/core/port/v1/query.proto +++ /dev/null @@ -1,35 +0,0 @@ -syntax = "proto3"; - -package ibc.core.port.v1; - -option go_package = "github.com/cosmos/ibc-go/v3/modules/core/05-port/types"; - -import "ibc/core/channel/v1/channel.proto"; - -// Query defines the gRPC querier service -service Query { - // AppVersion queries an IBC Port and determines the appropriate application version to be used - rpc AppVersion(QueryAppVersionRequest) returns (QueryAppVersionResponse) {} -} - -// QueryAppVersionRequest is the request type for the Query/AppVersion RPC method -message QueryAppVersionRequest { - // port unique identifier - string port_id = 1; - // connection unique identifier - string connection_id = 2; - // whether the channel is ordered or unordered - ibc.core.channel.v1.Order ordering = 3; - // counterparty channel end - ibc.core.channel.v1.Counterparty counterparty = 4; - // proposed version - string proposed_version = 5; -} - -// QueryAppVersionResponse is the response type for the Query/AppVersion RPC method. -message QueryAppVersionResponse { - // port id associated with the request identifiers - string port_id = 1; - // supported app version - string version = 2; -} diff --git a/testing/endpoint.go b/testing/endpoint.go index a2ab014fe99..4c3804879c9 100644 --- a/testing/endpoint.go +++ b/testing/endpoint.go @@ -306,6 +306,10 @@ func (endpoint *Endpoint) ChanOpenTry() error { require.NoError(endpoint.Chain.t, 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 + return nil } diff --git a/testing/mock/ibc_app.go b/testing/mock/ibc_app.go index a4a7460c05f..a3f2db3bc6d 100644 --- a/testing/mock/ibc_app.go +++ b/testing/mock/ibc_app.go @@ -8,6 +8,7 @@ import ( "github.com/cosmos/ibc-go/v3/modules/core/exported" ) +// MockIBCApp contains IBC application module callbacks as defined in 05-port. type MockIBCApp struct { OnChanOpenInit func( ctx sdk.Context, @@ -28,9 +29,8 @@ type MockIBCApp struct { channelID string, channelCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, - version, counterpartyVersion string, - ) error + ) (version string, err error) OnChanOpenAck func( ctx sdk.Context, @@ -80,16 +80,4 @@ type MockIBCApp struct { packet channeltypes.Packet, relayer sdk.AccAddress, ) error - - // 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 func( - ctx sdk.Context, - order channeltypes.Order, - connectionID string, - portID string, - counterparty channeltypes.Counterparty, - proposedVersion string, - ) (version string, err error) } diff --git a/testing/mock/ibc_module.go b/testing/mock/ibc_module.go index 128f17cf799..84e57b8f25d 100644 --- a/testing/mock/ibc_module.go +++ b/testing/mock/ibc_module.go @@ -2,7 +2,6 @@ package mock import ( "bytes" - "errors" "strconv" sdk "github.com/cosmos/cosmos-sdk/types" @@ -49,18 +48,18 @@ func (im IBCModule) OnChanOpenInit( // OnChanOpenTry implements the IBCModule interface. func (im IBCModule) OnChanOpenTry( ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID string, - channelID string, chanCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, version, counterpartyVersion string, -) error { + channelID string, chanCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, counterpartyVersion string, +) (version string, err error) { if im.IBCApp.OnChanOpenTry != nil { - return im.IBCApp.OnChanOpenTry(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, version, counterpartyVersion) + return im.IBCApp.OnChanOpenTry(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, counterpartyVersion) } // Claim channel capability passed back by IBC module if err := im.scopedKeeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { - return err + return "", err } - return nil + return Version, nil } // OnChanOpenAck implements the IBCModule interface. @@ -152,23 +151,3 @@ func (im IBCModule) OnTimeoutPacket(ctx sdk.Context, packet channeltypes.Packet, return nil } - -// NegotiateAppVersion implements the IBCModule interface. -func (im IBCModule) NegotiateAppVersion( - ctx sdk.Context, - order channeltypes.Order, - connectionID string, - portID string, - counterparty channeltypes.Counterparty, - proposedVersion string, -) (string, error) { - if im.IBCApp.NegotiateAppVersion != nil { - return im.IBCApp.NegotiateAppVersion(ctx, order, connectionID, portID, counterparty, proposedVersion) - } - - if proposedVersion != Version { // allow testing of error scenarios - return "", errors.New("failed to negotiate app version") - } - - return Version, nil -} diff --git a/testing/simapp/app.go b/testing/simapp/app.go index b5e7751b513..0287dc0495b 100644 --- a/testing/simapp/app.go +++ b/testing/simapp/app.go @@ -341,7 +341,7 @@ func NewSimApp( // Create Transfer Keepers app.TransferKeeper = ibctransferkeeper.NewKeeper( appCodec, keys[ibctransfertypes.StoreKey], app.GetSubspace(ibctransfertypes.ModuleName), - app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, app.AccountKeeper, app.BankKeeper, scopedTransferKeeper, ) transferModule := transfer.NewAppModule(app.TransferKeeper) diff --git a/testing/values.go b/testing/values.go index e341b98fd50..8dbfa66d3ab 100644 --- a/testing/values.go +++ b/testing/values.go @@ -29,7 +29,7 @@ const ( MaxClockDrift time.Duration = time.Second * 10 DefaultDelayPeriod uint64 = 0 - DefaultChannelVersion = ibctransfertypes.Version + DefaultChannelVersion = mock.Version InvalidID = "IDisInvalid" // Application Ports From 151907a7058b70ac3f97c2bffaaa22cc9d6f9f15 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 10 Jan 2022 12:16:42 +0100 Subject: [PATCH 002/140] improve 04-channel logging (#692) (#698) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description closes: #674 --- 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. - [x] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [x] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [x] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [x] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [x] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [x] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [x] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [x] Re-reviewed `Files changed` in the Github PR explorer - [x] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit 087bc5d77a194f8ea0010942017060357fb62f12) Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> --- CHANGELOG.md | 1 + modules/core/04-channel/keeper/packet.go | 29 +++++++++++++++++++---- modules/core/04-channel/keeper/timeout.go | 10 ++++++-- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f1ecf9039f..d0d612dfa1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### 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. * [\#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/issues/288) Making the 05-port keeper function IsBound public. The IsBound function checks if the provided portID is already binded to a module. * (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. diff --git a/modules/core/04-channel/keeper/packet.go b/modules/core/04-channel/keeper/packet.go index 58e63ad976d..8eaa69bf3e5 100644 --- a/modules/core/04-channel/keeper/packet.go +++ b/modules/core/04-channel/keeper/packet.go @@ -2,7 +2,6 @@ package keeper import ( "bytes" - "fmt" "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -135,6 +134,7 @@ func (k Keeper) SendPacket( "dst_port", packet.GetDestPort(), "dst_channel", packet.GetDestChannel(), ) + return nil } @@ -281,7 +281,14 @@ func (k Keeper) RecvPacket( } // log that a packet has been received & executed - k.Logger(ctx).Info("packet received", "packet", fmt.Sprintf("%v", packet)) + k.Logger(ctx).Info( + "packet received", + "sequence", packet.GetSequence(), + "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) @@ -345,7 +352,14 @@ func (k Keeper) WriteAcknowledgement( ) // log that a packet acknowledgement has been written - k.Logger(ctx).Info("acknowledged written", "packet", fmt.Sprintf("%v", packet)) + k.Logger(ctx).Info( + "acknowledgement written", + "sequence", packet.GetSequence, + "src_port", packet.GetSourcePort(), + "src_channel", packet.GetSourceChannel(), + "dst_port", packet.GetDestPort(), + "dst_channel", packet.GetDestChannel(), + ) EmitWriteAcknowledgementEvent(ctx, packet, channel, acknowledgement) @@ -472,7 +486,14 @@ func (k Keeper) AcknowledgePacket( k.deletePacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) // log that a packet has been acknowledged - k.Logger(ctx).Info("packet acknowledged", "packet", fmt.Sprintf("%v", packet)) + k.Logger(ctx).Info( + "packet acknowledged", + "sequence", packet.GetSequence, + "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) diff --git a/modules/core/04-channel/keeper/timeout.go b/modules/core/04-channel/keeper/timeout.go index ae7039ef0b2..ff75b08aea4 100644 --- a/modules/core/04-channel/keeper/timeout.go +++ b/modules/core/04-channel/keeper/timeout.go @@ -2,7 +2,6 @@ package keeper import ( "bytes" - "fmt" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -157,7 +156,14 @@ func (k Keeper) TimeoutExecuted( k.SetChannel(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), channel) } - k.Logger(ctx).Info("packet timed-out", "packet", fmt.Sprintf("%v", packet)) + k.Logger(ctx).Info( + "packet timed-out", + "sequence", packet.GetSequence(), + "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) From 6738a96c9ac2b06404e5a57e7e5c6f3b0ab3e701 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 12 Jan 2022 07:53:20 +0100 Subject: [PATCH 003/140] add cli to get denom hash from trace path and base denom (#679) (#711) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * get denom hash from denom trace * revert .gitignore * final revert * ignore history files * Update modules/apps/transfer/client/cli/query.go Co-authored-by: Carlos Rodriguez * Update modules/apps/transfer/keeper/grpc_query.go Co-authored-by: Carlos Rodriguez * Update modules/apps/transfer/keeper/keeper.go Co-authored-by: Carlos Rodriguez * Update modules/apps/transfer/keeper/grpc_query.go Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> * code review feedback integration * moved new feature to unreleased section * fix duplicate block in success test case * fix CHANGELOG format * fix invalid trace path argument error handling * refactor positive test case code Co-authored-by: Carlos Rodriguez Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> (cherry picked from commit cb9bf5dbb283e5e0eb0c29ee658968019bf58f49) Co-authored-by: nir1218 --- .gitignore | 3 + CHANGELOG.md | 1 + docs/client/swagger-ui/swagger.yaml | 66 +++ docs/ibc/proto-docs.md | 35 ++ modules/apps/transfer/client/cli/cli.go | 1 + modules/apps/transfer/client/cli/query.go | 32 ++ modules/apps/transfer/keeper/grpc_query.go | 27 ++ .../apps/transfer/keeper/grpc_query_test.go | 67 +++ modules/apps/transfer/types/query.pb.go | 458 ++++++++++++++++-- modules/apps/transfer/types/query.pb.gw.go | 98 ++++ .../ibc/applications/transfer/v1/query.proto | 19 + 11 files changed, 772 insertions(+), 35 deletions(-) diff --git a/.gitignore b/.gitignore index 2bf18165980..e821f35d7e7 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,6 @@ dependency-graph.png *.aux *.out *.synctex.gz + +*.history + diff --git a/CHANGELOG.md b/CHANGELOG.md index d0d612dfa1a..4d2f9fd72dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * [\#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 diff --git a/docs/client/swagger-ui/swagger.yaml b/docs/client/swagger-ui/swagger.yaml index 835c894c1a9..5feb43dd2a5 100644 --- a/docs/client/swagger-ui/swagger.yaml +++ b/docs/client/swagger-ui/swagger.yaml @@ -4,6 +4,54 @@ info: description: A REST interface for state queries version: 1.0.0 paths: + '/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/denom_traces: get: summary: DenomTraces queries all denomination traces. @@ -7326,6 +7374,15 @@ paths: required: false type: boolean format: 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}': @@ -9839,6 +9896,15 @@ definitions: 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.QueryDenomTraceResponse: type: object properties: diff --git a/docs/ibc/proto-docs.md b/docs/ibc/proto-docs.md index 8baaa8eda1a..921a3326522 100644 --- a/docs/ibc/proto-docs.md +++ b/docs/ibc/proto-docs.md @@ -28,6 +28,8 @@ - [GenesisState](#ibc.applications.transfer.v1.GenesisState) - [ibc/applications/transfer/v1/query.proto](#ibc/applications/transfer/v1/query.proto) + - [QueryDenomHashRequest](#ibc.applications.transfer.v1.QueryDenomHashRequest) + - [QueryDenomHashResponse](#ibc.applications.transfer.v1.QueryDenomHashResponse) - [QueryDenomTraceRequest](#ibc.applications.transfer.v1.QueryDenomTraceRequest) - [QueryDenomTraceResponse](#ibc.applications.transfer.v1.QueryDenomTraceResponse) - [QueryDenomTracesRequest](#ibc.applications.transfer.v1.QueryDenomTracesRequest) @@ -549,6 +551,38 @@ GenesisState defines the ibc-transfer genesis state + + +### QueryDenomHashRequest +QueryDenomHashRequest is the request type for the Query/DenomHash RPC +method + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `trace` | [string](#string) | | The denomination trace ([port_id]/[channel_id])+/[denom] | + + + + + + + + +### QueryDenomHashResponse +QueryDenomHashResponse is the response type for the Query/DenomHash RPC +method. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `hash` | [string](#string) | | hash (in hex format) of the denomination trace information. | + + + + + + ### QueryDenomTraceRequest @@ -655,6 +689,7 @@ Query provides defines the gRPC querier service. | `DenomTrace` | [QueryDenomTraceRequest](#ibc.applications.transfer.v1.QueryDenomTraceRequest) | [QueryDenomTraceResponse](#ibc.applications.transfer.v1.QueryDenomTraceResponse) | DenomTrace queries a denomination trace information. | GET|/ibc/apps/transfer/v1/denom_traces/{hash}| | `DenomTraces` | [QueryDenomTracesRequest](#ibc.applications.transfer.v1.QueryDenomTracesRequest) | [QueryDenomTracesResponse](#ibc.applications.transfer.v1.QueryDenomTracesResponse) | DenomTraces queries all denomination traces. | GET|/ibc/apps/transfer/v1/denom_traces| | `Params` | [QueryParamsRequest](#ibc.applications.transfer.v1.QueryParamsRequest) | [QueryParamsResponse](#ibc.applications.transfer.v1.QueryParamsResponse) | Params queries all parameters of the ibc-transfer module. | GET|/ibc/apps/transfer/v1/params| +| `DenomHash` | [QueryDenomHashRequest](#ibc.applications.transfer.v1.QueryDenomHashRequest) | [QueryDenomHashResponse](#ibc.applications.transfer.v1.QueryDenomHashResponse) | DenomHash queries a denomination hash information. | GET|/ibc/apps/transfer/v1/denom_hashes/{trace}| diff --git a/modules/apps/transfer/client/cli/cli.go b/modules/apps/transfer/client/cli/cli.go index 643af504178..de83f4d4736 100644 --- a/modules/apps/transfer/client/cli/cli.go +++ b/modules/apps/transfer/client/cli/cli.go @@ -20,6 +20,7 @@ func GetQueryCmd() *cobra.Command { GetCmdQueryDenomTraces(), GetCmdParams(), GetCmdQueryEscrowAddress(), + GetCmdQueryDenomHash(), ) return queryCmd diff --git a/modules/apps/transfer/client/cli/query.go b/modules/apps/transfer/client/cli/query.go index 78d7bfd1b99..de585061ccf 100644 --- a/modules/apps/transfer/client/cli/query.go +++ b/modules/apps/transfer/client/cli/query.go @@ -135,3 +135,35 @@ func GetCmdQueryEscrowAddress() *cobra.Command { 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 [denom_trace]", 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 +} diff --git a/modules/apps/transfer/keeper/grpc_query.go b/modules/apps/transfer/keeper/grpc_query.go index 8e8e4a807f4..e3310094c2d 100644 --- a/modules/apps/transfer/keeper/grpc_query.go +++ b/modules/apps/transfer/keeper/grpc_query.go @@ -81,3 +81,30 @@ func (q Keeper) Params(c context.Context, _ *types.QueryParamsRequest) (*types.Q Params: ¶ms, }, nil } + +// DenomHash implements the Query/DenomHash gRPC method +func (q Keeper) DenomHash(c context.Context, req *types.QueryDenomHashRequest) (*types.QueryDenomHashResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + // Convert given request trace path to DenomTrace struct to confirm the path in a valid denom trace format + denomTrace := types.ParseDenomTrace(req.Trace) + if err := denomTrace.Validate(); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + ctx := sdk.UnwrapSDKContext(c) + denomHash := denomTrace.Hash() + found := q.HasDenomTrace(ctx, denomHash) + if !found { + return nil, status.Error( + codes.NotFound, + sdkerrors.Wrap(types.ErrTraceNotFound, req.Trace).Error(), + ) + } + + return &types.QueryDenomHashResponse{ + Hash: denomHash.String(), + }, nil +} diff --git a/modules/apps/transfer/keeper/grpc_query_test.go b/modules/apps/transfer/keeper/grpc_query_test.go index 61469ebf593..5d65ed3d839 100644 --- a/modules/apps/transfer/keeper/grpc_query_test.go +++ b/modules/apps/transfer/keeper/grpc_query_test.go @@ -140,3 +140,70 @@ func (suite *KeeperTestSuite) TestQueryParams() { res, _ := suite.queryClient.Params(ctx, &types.QueryParamsRequest{}) suite.Require().Equal(&expParams, res.Params) } + +func (suite *KeeperTestSuite) TestQueryDenomHash() { + + reqTrace := types.DenomTrace{ + Path: "transfer/channelToA/transfer/channelToB", + BaseDenom: "uatom", + } + + var ( + req *types.QueryDenomHashRequest + expHash = reqTrace.Hash().String() + ) + + testCases := []struct { + msg string + malleate func() + expPass bool + }{ + { + "invalid trace", + func() { + req = &types.QueryDenomHashRequest{ + Trace: "transfer/channelToA/transfer/", + } + }, + false, + }, + { + "not found denom trace", + func() { + req = &types.QueryDenomHashRequest{ + Trace: "transfer/channelToC/uatom", + } + }, + false, + }, + { + "success", + func() {}, + true, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + req = &types.QueryDenomHashRequest{ + Trace: reqTrace.GetFullDenomPath(), + } + suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace(suite.chainA.GetContext(), reqTrace) + + tc.malleate() + ctx := sdk.WrapSDKContext(suite.chainA.GetContext()) + + res, err := suite.queryClient.DenomHash(ctx, req) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expHash, res.Hash) + } else { + suite.Require().Error(err) + } + }) + } +} diff --git a/modules/apps/transfer/types/query.pb.go b/modules/apps/transfer/types/query.pb.go index ee8a05e63cc..024da758162 100644 --- a/modules/apps/transfer/types/query.pb.go +++ b/modules/apps/transfer/types/query.pb.go @@ -310,6 +310,100 @@ func (m *QueryParamsResponse) GetParams() *Params { 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 "" +} + func init() { proto.RegisterType((*QueryDenomTraceRequest)(nil), "ibc.applications.transfer.v1.QueryDenomTraceRequest") proto.RegisterType((*QueryDenomTraceResponse)(nil), "ibc.applications.transfer.v1.QueryDenomTraceResponse") @@ -317,6 +411,8 @@ func init() { proto.RegisterType((*QueryDenomTracesResponse)(nil), "ibc.applications.transfer.v1.QueryDenomTracesResponse") proto.RegisterType((*QueryParamsRequest)(nil), "ibc.applications.transfer.v1.QueryParamsRequest") proto.RegisterType((*QueryParamsResponse)(nil), "ibc.applications.transfer.v1.QueryParamsResponse") + proto.RegisterType((*QueryDenomHashRequest)(nil), "ibc.applications.transfer.v1.QueryDenomHashRequest") + proto.RegisterType((*QueryDenomHashResponse)(nil), "ibc.applications.transfer.v1.QueryDenomHashResponse") } func init() { @@ -324,41 +420,45 @@ func init() { } var fileDescriptor_a638e2800a01538c = []byte{ - // 532 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x54, 0xcd, 0x6e, 0xd3, 0x40, - 0x10, 0xce, 0xb6, 0x10, 0x89, 0x09, 0xe2, 0xb0, 0x54, 0x10, 0x59, 0x91, 0x5b, 0x59, 0x11, 0x84, - 0x16, 0x76, 0x71, 0xcb, 0xcf, 0x85, 0x53, 0x85, 0x40, 0xdc, 0xda, 0xc0, 0x09, 0x0e, 0x68, 0xed, - 0x2c, 0x8e, 0xa5, 0xd8, 0xeb, 0x7a, 0x9d, 0x48, 0x15, 0xe2, 0xc2, 0x13, 0x20, 0xf5, 0x25, 0x10, - 0xe2, 0x21, 0x38, 0xf6, 0x58, 0x89, 0x0b, 0x27, 0x40, 0x09, 0xef, 0xc0, 0x15, 0x79, 0x77, 0xdd, - 0xd8, 0xa4, 0x4a, 0xeb, 0xdb, 0x6a, 0x3c, 0xdf, 0x7c, 0x3f, 0x33, 0x32, 0xf4, 0x42, 0xcf, 0xa7, - 0x2c, 0x49, 0x46, 0xa1, 0xcf, 0xb2, 0x50, 0xc4, 0x92, 0x66, 0x29, 0x8b, 0xe5, 0x3b, 0x9e, 0xd2, - 0x89, 0x4b, 0x0f, 0xc6, 0x3c, 0x3d, 0x24, 0x49, 0x2a, 0x32, 0x81, 0x3b, 0xa1, 0xe7, 0x93, 0x72, - 0x27, 0x29, 0x3a, 0xc9, 0xc4, 0xb5, 0xd6, 0x02, 0x11, 0x08, 0xd5, 0x48, 0xf3, 0x97, 0xc6, 0x58, - 0x9b, 0xbe, 0x90, 0x91, 0x90, 0xd4, 0x63, 0x92, 0xeb, 0x61, 0x74, 0xe2, 0x7a, 0x3c, 0x63, 0x2e, - 0x4d, 0x58, 0x10, 0xc6, 0x6a, 0x90, 0xe9, 0xdd, 0x5a, 0xaa, 0xe4, 0x94, 0x4b, 0x37, 0x77, 0x02, - 0x21, 0x82, 0x11, 0xa7, 0x2c, 0x09, 0x29, 0x8b, 0x63, 0x91, 0x19, 0x49, 0xea, 0xab, 0x73, 0x17, - 0x6e, 0xec, 0xe7, 0x64, 0x4f, 0x79, 0x2c, 0xa2, 0x57, 0x29, 0xf3, 0x79, 0x9f, 0x1f, 0x8c, 0xb9, - 0xcc, 0x30, 0x86, 0x4b, 0x43, 0x26, 0x87, 0x6d, 0xb4, 0x81, 0x7a, 0x57, 0xfa, 0xea, 0xed, 0x0c, - 0xe0, 0xe6, 0x42, 0xb7, 0x4c, 0x44, 0x2c, 0x39, 0x7e, 0x01, 0xad, 0x41, 0x5e, 0x7d, 0x9b, 0xe5, - 0x65, 0x85, 0x6a, 0x6d, 0xf7, 0xc8, 0xb2, 0x24, 0x48, 0x69, 0x0c, 0x0c, 0x4e, 0xdf, 0x0e, 0x5b, - 0x60, 0x91, 0x85, 0xa8, 0x67, 0x00, 0xf3, 0x34, 0x0c, 0xc9, 0x2d, 0xa2, 0xa3, 0x23, 0x79, 0x74, - 0x44, 0xef, 0xc1, 0x44, 0x47, 0xf6, 0x58, 0x50, 0x18, 0xea, 0x97, 0x90, 0xce, 0x37, 0x04, 0xed, - 0x45, 0x0e, 0x63, 0xe5, 0x0d, 0x5c, 0x2d, 0x59, 0x91, 0x6d, 0xb4, 0xb1, 0x5a, 0xc7, 0xcb, 0xee, - 0xb5, 0xe3, 0x9f, 0xeb, 0x8d, 0x2f, 0xbf, 0xd6, 0x9b, 0x66, 0x6e, 0x6b, 0xee, 0x4d, 0xe2, 0xe7, - 0x15, 0x07, 0x2b, 0xca, 0xc1, 0xed, 0x73, 0x1d, 0x68, 0x65, 0x15, 0x0b, 0x6b, 0x80, 0x95, 0x83, - 0x3d, 0x96, 0xb2, 0xa8, 0x08, 0xc8, 0x79, 0x09, 0xd7, 0x2b, 0x55, 0x63, 0xe9, 0x09, 0x34, 0x13, - 0x55, 0x31, 0x99, 0x75, 0x97, 0x9b, 0x31, 0x68, 0x83, 0xd9, 0xfe, 0xbb, 0x0a, 0x97, 0xd5, 0x54, - 0xfc, 0x15, 0x01, 0xcc, 0x9d, 0xe2, 0x07, 0xcb, 0xc7, 0x9c, 0x7d, 0x59, 0xd6, 0xc3, 0x9a, 0x28, - 0xed, 0xc1, 0x71, 0x3f, 0x7e, 0xff, 0x73, 0xb4, 0xb2, 0x85, 0xef, 0x50, 0x73, 0xfe, 0xd5, 0xb3, - 0x2f, 0xaf, 0x8c, 0xbe, 0xcf, 0xcf, 0xf5, 0x03, 0xfe, 0x8c, 0xa0, 0x55, 0xda, 0x30, 0xae, 0xc7, - 0x5c, 0x84, 0x6a, 0x3d, 0xaa, 0x0b, 0x33, 0x8a, 0x37, 0x95, 0xe2, 0x2e, 0x76, 0xce, 0x57, 0x8c, - 0x8f, 0x10, 0x34, 0x75, 0xec, 0xf8, 0xfe, 0x05, 0xe8, 0x2a, 0x5b, 0xb7, 0xdc, 0x1a, 0x08, 0xa3, - 0xad, 0xab, 0xb4, 0xd9, 0xb8, 0x73, 0xb6, 0x36, 0xbd, 0xf9, 0xdd, 0xfd, 0xe3, 0xa9, 0x8d, 0x4e, - 0xa6, 0x36, 0xfa, 0x3d, 0xb5, 0xd1, 0xa7, 0x99, 0xdd, 0x38, 0x99, 0xd9, 0x8d, 0x1f, 0x33, 0xbb, - 0xf1, 0xfa, 0x71, 0x10, 0x66, 0xc3, 0xb1, 0x47, 0x7c, 0x11, 0x51, 0xf3, 0xeb, 0x0a, 0x3d, 0xff, - 0x5e, 0x20, 0xe8, 0x64, 0x87, 0x46, 0x62, 0x30, 0x1e, 0x71, 0xf9, 0xdf, 0xd8, 0xec, 0x30, 0xe1, - 0xd2, 0x6b, 0xaa, 0x1f, 0xcf, 0xce, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x64, 0xab, 0xc6, 0xb0, - 0x4f, 0x05, 0x00, 0x00, + // 595 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x54, 0x4f, 0x6f, 0xd3, 0x30, + 0x1c, 0xad, 0x07, 0xab, 0x34, 0x17, 0x71, 0x30, 0x05, 0xaa, 0xa8, 0xca, 0xa6, 0xa8, 0x82, 0xd2, + 0x6d, 0x36, 0x69, 0x07, 0x5c, 0x38, 0x4d, 0x88, 0x3f, 0xb7, 0xad, 0x70, 0x82, 0x03, 0x72, 0x52, + 0x93, 0x46, 0x6a, 0xe3, 0x2c, 0x4e, 0x2b, 0x4d, 0x68, 0x17, 0x3e, 0x01, 0xd2, 0xbe, 0x02, 0x07, + 0x34, 0xf1, 0x21, 0x38, 0xee, 0x38, 0x89, 0x0b, 0x27, 0x40, 0x2d, 0x1f, 0x04, 0xc5, 0x76, 0xda, + 0x84, 0x56, 0xdd, 0x72, 0x73, 0xdd, 0xdf, 0xfb, 0xfd, 0xde, 0x7b, 0xbf, 0x17, 0xc3, 0xa6, 0xef, + 0xb8, 0x84, 0x86, 0xe1, 0xc0, 0x77, 0x69, 0xec, 0xf3, 0x40, 0x90, 0x38, 0xa2, 0x81, 0xf8, 0xc0, + 0x22, 0x32, 0xb6, 0xc9, 0xd1, 0x88, 0x45, 0xc7, 0x38, 0x8c, 0x78, 0xcc, 0x51, 0xdd, 0x77, 0x5c, + 0x9c, 0xad, 0xc4, 0x69, 0x25, 0x1e, 0xdb, 0x46, 0xd5, 0xe3, 0x1e, 0x97, 0x85, 0x24, 0x39, 0x29, + 0x8c, 0xd1, 0x72, 0xb9, 0x18, 0x72, 0x41, 0x1c, 0x2a, 0x98, 0x6a, 0x46, 0xc6, 0xb6, 0xc3, 0x62, + 0x6a, 0x93, 0x90, 0x7a, 0x7e, 0x20, 0x1b, 0xe9, 0xda, 0xed, 0x95, 0x4c, 0x66, 0xb3, 0x54, 0x71, + 0xdd, 0xe3, 0xdc, 0x1b, 0x30, 0x42, 0x43, 0x9f, 0xd0, 0x20, 0xe0, 0xb1, 0xa6, 0x24, 0xff, 0xb5, + 0x76, 0xe0, 0x9d, 0xc3, 0x64, 0xd8, 0x33, 0x16, 0xf0, 0xe1, 0x9b, 0x88, 0xba, 0xac, 0xcb, 0x8e, + 0x46, 0x4c, 0xc4, 0x08, 0xc1, 0xeb, 0x7d, 0x2a, 0xfa, 0x35, 0xb0, 0x05, 0x9a, 0x1b, 0x5d, 0x79, + 0xb6, 0x7a, 0xf0, 0xee, 0x42, 0xb5, 0x08, 0x79, 0x20, 0x18, 0x7a, 0x05, 0x2b, 0xbd, 0xe4, 0xf6, + 0x7d, 0x9c, 0x5c, 0x4b, 0x54, 0xa5, 0xdd, 0xc4, 0xab, 0x9c, 0xc0, 0x99, 0x36, 0xb0, 0x37, 0x3b, + 0x5b, 0x74, 0x61, 0x8a, 0x48, 0x49, 0x3d, 0x87, 0x70, 0xee, 0x86, 0x1e, 0x72, 0x0f, 0x2b, 0xeb, + 0x70, 0x62, 0x1d, 0x56, 0x7b, 0xd0, 0xd6, 0xe1, 0x03, 0xea, 0xa5, 0x82, 0xba, 0x19, 0xa4, 0xf5, + 0x1d, 0xc0, 0xda, 0xe2, 0x0c, 0x2d, 0xe5, 0x1d, 0xbc, 0x91, 0x91, 0x22, 0x6a, 0x60, 0xeb, 0x5a, + 0x11, 0x2d, 0xfb, 0x37, 0xcf, 0x7f, 0x6d, 0x96, 0xce, 0x7e, 0x6f, 0x96, 0x75, 0xdf, 0xca, 0x5c, + 0x9b, 0x40, 0x2f, 0x72, 0x0a, 0xd6, 0xa4, 0x82, 0xfb, 0x97, 0x2a, 0x50, 0xcc, 0x72, 0x12, 0xaa, + 0x10, 0x49, 0x05, 0x07, 0x34, 0xa2, 0xc3, 0xd4, 0x20, 0xeb, 0x35, 0xbc, 0x95, 0xbb, 0xd5, 0x92, + 0x9e, 0xc2, 0x72, 0x28, 0x6f, 0xb4, 0x67, 0x8d, 0xd5, 0x62, 0x34, 0x5a, 0x63, 0xac, 0x5d, 0x78, + 0x7b, 0x6e, 0xd6, 0x4b, 0x2a, 0xfa, 0xe9, 0x3a, 0xaa, 0x70, 0x7d, 0xbe, 0xee, 0x8d, 0xae, 0xfa, + 0x91, 0xcf, 0x94, 0x2a, 0xd7, 0x34, 0x96, 0x64, 0xaa, 0xfd, 0x65, 0x1d, 0xae, 0xcb, 0x72, 0xf4, + 0x0d, 0x40, 0x38, 0xb7, 0x11, 0xed, 0xad, 0xe6, 0xb8, 0x3c, 0xb6, 0xc6, 0xa3, 0x82, 0x28, 0xc5, + 0xcc, 0xb2, 0x3f, 0xfd, 0xf8, 0x7b, 0xba, 0xb6, 0x8d, 0x1e, 0x10, 0xfd, 0x6d, 0xe5, 0xbf, 0xa9, + 0x6c, 0x1e, 0xc8, 0xc7, 0x84, 0xf7, 0x09, 0xfa, 0x0a, 0x60, 0x25, 0x13, 0x1f, 0x54, 0x6c, 0x72, + 0xba, 0x31, 0xe3, 0x71, 0x51, 0x98, 0x66, 0xdc, 0x92, 0x8c, 0x1b, 0xc8, 0xba, 0x9c, 0x31, 0x3a, + 0x05, 0xb0, 0xac, 0x76, 0x8a, 0x1e, 0x5e, 0x61, 0x5c, 0x2e, 0x52, 0x86, 0x5d, 0x00, 0xa1, 0xb9, + 0x35, 0x24, 0x37, 0x13, 0xd5, 0x97, 0x73, 0x53, 0xb1, 0x42, 0x67, 0x00, 0x6e, 0xcc, 0x32, 0x82, + 0x3a, 0x57, 0xf5, 0x21, 0x13, 0x40, 0x63, 0xaf, 0x18, 0x48, 0xd3, 0x6b, 0x4b, 0x7a, 0x3b, 0xa8, + 0xb5, 0xca, 0xba, 0x64, 0xc9, 0xc9, 0xb2, 0xa5, 0x85, 0x27, 0xfb, 0x87, 0xe7, 0x13, 0x13, 0x5c, + 0x4c, 0x4c, 0xf0, 0x67, 0x62, 0x82, 0xcf, 0x53, 0xb3, 0x74, 0x31, 0x35, 0x4b, 0x3f, 0xa7, 0x66, + 0xe9, 0xed, 0x13, 0xcf, 0x8f, 0xfb, 0x23, 0x07, 0xbb, 0x7c, 0x48, 0xf4, 0x23, 0xee, 0x3b, 0xee, + 0xae, 0xc7, 0xc9, 0xb8, 0x43, 0x86, 0xbc, 0x37, 0x1a, 0x30, 0xf1, 0xdf, 0x90, 0xf8, 0x38, 0x64, + 0xc2, 0x29, 0xcb, 0x27, 0xb8, 0xf3, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x16, 0x01, 0x88, 0xe2, 0x59, + 0x06, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -379,6 +479,8 @@ type QueryClient interface { DenomTraces(ctx context.Context, in *QueryDenomTracesRequest, opts ...grpc.CallOption) (*QueryDenomTracesResponse, error) // Params queries all parameters of the ibc-transfer module. Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) + // DenomHash queries a denomination hash information. + DenomHash(ctx context.Context, in *QueryDenomHashRequest, opts ...grpc.CallOption) (*QueryDenomHashResponse, error) } type queryClient struct { @@ -416,6 +518,15 @@ func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts . 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 +} + // QueryServer is the server API for Query service. type QueryServer interface { // DenomTrace queries a denomination trace information. @@ -424,6 +535,8 @@ type QueryServer interface { DenomTraces(context.Context, *QueryDenomTracesRequest) (*QueryDenomTracesResponse, error) // Params queries all parameters of the ibc-transfer module. Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) + // DenomHash queries a denomination hash information. + DenomHash(context.Context, *QueryDenomHashRequest) (*QueryDenomHashResponse, error) } // UnimplementedQueryServer can be embedded to have forward compatible implementations. @@ -439,6 +552,9 @@ func (*UnimplementedQueryServer) DenomTraces(ctx context.Context, req *QueryDeno func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") } +func (*UnimplementedQueryServer) DenomHash(ctx context.Context, req *QueryDenomHashRequest) (*QueryDenomHashResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DenomHash not implemented") +} func RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) @@ -498,6 +614,24 @@ func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interf 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) +} + var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "ibc.applications.transfer.v1.Query", HandlerType: (*QueryServer)(nil), @@ -514,6 +648,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "Params", Handler: _Query_Params_Handler, }, + { + MethodName: "DenomHash", + Handler: _Query_DenomHash_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "ibc/applications/transfer/v1/query.proto", @@ -726,6 +864,66 @@ func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { 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 encodeVarintQuery(dAtA []byte, offset int, v uint64) int { offset -= sovQuery(v) base := offset @@ -817,6 +1015,32 @@ func (m *QueryParamsResponse) Size() (n int) { 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 sovQuery(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -1333,6 +1557,170 @@ func (m *QueryParamsResponse) Unmarshal(dAtA []byte) error { } 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 skipQuery(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/modules/apps/transfer/types/query.pb.gw.go b/modules/apps/transfer/types/query.pb.gw.go index 0cd48139ccc..6f17d4dc055 100644 --- a/modules/apps/transfer/types/query.pb.gw.go +++ b/modules/apps/transfer/types/query.pb.gw.go @@ -139,6 +139,60 @@ func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshal } +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 + +} + // 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. @@ -205,6 +259,26 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + 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.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) + 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()...) + + }) + return nil } @@ -306,6 +380,26 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + 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()...) + + }) + return nil } @@ -315,6 +409,8 @@ var ( pattern_Query_DenomTraces_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"ibc", "apps", "transfer", "v1", "denom_traces"}, "", runtime.AssumeColonVerbOpt(true))) 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(true))) + + pattern_Query_DenomHash_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", "apps", "transfer", "v1", "denom_hashes", "trace"}, "", runtime.AssumeColonVerbOpt(true))) ) var ( @@ -323,4 +419,6 @@ var ( forward_Query_DenomTraces_0 = runtime.ForwardResponseMessage forward_Query_Params_0 = runtime.ForwardResponseMessage + + forward_Query_DenomHash_0 = runtime.ForwardResponseMessage ) diff --git a/proto/ibc/applications/transfer/v1/query.proto b/proto/ibc/applications/transfer/v1/query.proto index 12876608db8..2ed28049fd7 100644 --- a/proto/ibc/applications/transfer/v1/query.proto +++ b/proto/ibc/applications/transfer/v1/query.proto @@ -25,6 +25,11 @@ service Query { rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { option (google.api.http).get = "/ibc/apps/transfer/v1/params"; } + + // DenomHash queries a denomination hash information. + rpc DenomHash(QueryDenomHashRequest) returns (QueryDenomHashResponse) { + option (google.api.http).get = "/ibc/apps/transfer/v1/denom_hashes/{trace}"; + } } // QueryDenomTraceRequest is the request type for the Query/DenomTrace RPC @@ -65,3 +70,17 @@ message QueryParamsResponse { // params defines the parameters of the module. Params params = 1; } + +// 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; +} From b4824936c009addae1ec6d3d25c444f56d808079 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 12 Jan 2022 21:37:08 +0100 Subject: [PATCH 004/140] removing unused proto imports in interchain-accounts (#718) (#720) (cherry picked from commit 0a7ad9b117596ac4b6ce6d597d124de0f2bc4eae) Co-authored-by: Damian Nolan --- .../interchain_accounts/controller/v1/controller.proto | 1 - .../applications/interchain_accounts/controller/v1/query.proto | 1 - proto/ibc/applications/interchain_accounts/host/v1/host.proto | 1 - proto/ibc/applications/interchain_accounts/host/v1/query.proto | 1 - 4 files changed, 4 deletions(-) diff --git a/proto/ibc/applications/interchain_accounts/controller/v1/controller.proto b/proto/ibc/applications/interchain_accounts/controller/v1/controller.proto index c43039ebb03..291f3e4fd90 100644 --- a/proto/ibc/applications/interchain_accounts/controller/v1/controller.proto +++ b/proto/ibc/applications/interchain_accounts/controller/v1/controller.proto @@ -4,7 +4,6 @@ package ibc.applications.interchain_accounts.controller.v1; option go_package = "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/controller/types"; -import "google/protobuf/any.proto"; import "gogoproto/gogo.proto"; // Params defines the set of on-chain interchain accounts parameters. diff --git a/proto/ibc/applications/interchain_accounts/controller/v1/query.proto b/proto/ibc/applications/interchain_accounts/controller/v1/query.proto index 63ceb8465da..d3960e9d14c 100644 --- a/proto/ibc/applications/interchain_accounts/controller/v1/query.proto +++ b/proto/ibc/applications/interchain_accounts/controller/v1/query.proto @@ -4,7 +4,6 @@ package ibc.applications.interchain_accounts.controller.v1; option go_package = "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/controller/types"; -import "gogoproto/gogo.proto"; import "ibc/applications/interchain_accounts/controller/v1/controller.proto"; import "google/api/annotations.proto"; diff --git a/proto/ibc/applications/interchain_accounts/host/v1/host.proto b/proto/ibc/applications/interchain_accounts/host/v1/host.proto index e3d6776ff79..a9d951cef82 100644 --- a/proto/ibc/applications/interchain_accounts/host/v1/host.proto +++ b/proto/ibc/applications/interchain_accounts/host/v1/host.proto @@ -4,7 +4,6 @@ package ibc.applications.interchain_accounts.host.v1; option go_package = "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/host/types"; -import "google/protobuf/any.proto"; import "gogoproto/gogo.proto"; // Params defines the set of on-chain interchain accounts parameters. diff --git a/proto/ibc/applications/interchain_accounts/host/v1/query.proto b/proto/ibc/applications/interchain_accounts/host/v1/query.proto index 3ec2ae8caef..5512d7b4fa5 100644 --- a/proto/ibc/applications/interchain_accounts/host/v1/query.proto +++ b/proto/ibc/applications/interchain_accounts/host/v1/query.proto @@ -5,7 +5,6 @@ package ibc.applications.interchain_accounts.host.v1; option go_package = "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/host/types"; import "google/api/annotations.proto"; -import "gogoproto/gogo.proto"; import "ibc/applications/interchain_accounts/host/v1/host.proto"; // Query provides defines the gRPC querier service. From 22fbe825790df06e8383a9c25680f62d239b2e25 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 27 Jan 2022 11:48:58 +0100 Subject: [PATCH 005/140] fix: support custom chain IDs for testing (#774) (#797) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Backport commits from main to v3 release branch (#682) * reorganize channel handshake handler (#647) * reorganize channel handshake handler split out channel state changes into its own function. * readjust 27-interchain-accounts to not rely on state being set before the application callback * add changelog and migration doc entry * Update modules/core/04-channel/keeper/handshake.go * docs: ICA Overview (#626) * docs: ica overview * fix: ordering * add spacing * fix: spacing * fix: remove bulletpoints * fix: wording * update go mod for security vulnerabilities (#655) * update go mod for security vulnerabilities * update package-lock.json * update vue dependency (#662) * bump glob-parent version in json package (#663) * build(deps): bump actions/setup-go from 2.1.4 to 2.1.5 (#656) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 2.1.4 to 2.1.5. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v2.1.4...v2.1.5) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> * docs: begin removal of internal "spec" directories (#634) * begin removal of spec docs within core ibc * remove broken link * Apply suggestions from code review Co-authored-by: Damian Nolan * remove broken link * remove broken links * Apply suggestions from code review Co-authored-by: Carlos Rodriguez Co-authored-by: Damian Nolan Co-authored-by: Carlos Rodriguez * Modify `OnChanOpenTry` application callback to perform app version negotitation (#646) * remove NegotiateAppVersion and AppVersion gRPC (#643) The NegotiateAppVersion callback has been removed from the IBC Application interface. The gRPC AppVersion has been removed. The app version negoitation will be handled by applications by returning the version in OnChanOpenTry. * Modify `OnChanOpenTry` to return application version (#650) * modify OnChanOpenTry to return negotiated version modify IBCModule interface function OnChanOpenTry to return the negotiated app version. Tests have not been updated * fix ibc_module_test.go tests * fix tests * Apply suggestions from code review * add handshake test case * add CHANGELOG and migration docs * update documentation * fix broken link * fix broken link (#664) * chore: update make build-docs, add docs build checker (#667) * update Makefile, add docs build checker * Update .github/workflows/check-docs.yml Co-authored-by: Marko Co-authored-by: Marko * register ICA query server, fix panics in params query cli (#666) Register the controller and host query servers to a chain. Returns an error upon cli params query failure instead of panicing. * update of roadmap with latest release (#653) * update of roadmap with latest release and changed the way release versions are encoded * fixed typo Co-authored-by: Carlos Rodriguez * build(deps): bump actions/checkout from 2.3.1 to 2.4.0 (#672) Bumps [actions/checkout](https://github.com/actions/checkout) from 2.3.1 to 2.4.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2.3.1...v2.4.0) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * allow ics20 to connect to middleware (#675) * allow ics20 to connect to middleware Creates ics4Wrapper which allows middleware applications to only implement SendPacket to connect ics20 as an application in its middleware stack * add changelog and migration doc * fix migration doc spelling * fix: register InterchainAccount as x/auth GenesisAccount (#676) * adding GenesisAccount interface registration for InterchainAccount impl * updating RegisterInterfaces ica godoc * enable mergify for backports (#678) Co-authored-by: Carlos Rodriguez Co-authored-by: Sean King Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Damian Nolan Co-authored-by: Carlos Rodriguez Co-authored-by: Marko Co-authored-by: Carlos Rodriguez * fixes evmos tests * cleanup * changelog and cleanup * pr suggestions. bump chainid Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> Co-authored-by: Sean King Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Damian Nolan Co-authored-by: Carlos Rodriguez Co-authored-by: Marko Co-authored-by: Carlos Rodriguez Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> (cherry picked from commit 8dfbc9cae2cf1f6811557feec4aa475801b64bfa) Co-authored-by: Ramiro Carlucho --- CHANGELOG.md | 1 + .../27-interchain-accounts/controller/ibc_module_test.go | 4 ++-- .../27-interchain-accounts/controller/keeper/keeper_test.go | 6 +++--- modules/apps/27-interchain-accounts/host/ibc_module_test.go | 4 ++-- .../apps/27-interchain-accounts/host/keeper/keeper_test.go | 6 +++--- modules/apps/27-interchain-accounts/types/account_test.go | 4 ++-- modules/apps/transfer/keeper/keeper_test.go | 6 +++--- modules/apps/transfer/transfer_test.go | 6 +++--- modules/core/02-client/abci_test.go | 4 ++-- modules/core/02-client/keeper/keeper_test.go | 4 ++-- modules/core/02-client/legacy/v100/store_test.go | 4 ++-- modules/core/02-client/types/msgs_test.go | 4 ++-- modules/core/03-connection/keeper/keeper_test.go | 4 ++-- modules/core/03-connection/types/msgs_test.go | 4 ++-- modules/core/04-channel/keeper/keeper_test.go | 4 ++-- modules/core/ante/ante_test.go | 4 ++-- modules/core/genesis_test.go | 4 ++-- modules/core/keeper/msg_server_test.go | 4 ++-- modules/core/legacy/v100/genesis_test.go | 4 ++-- .../light-clients/06-solomachine/types/solomachine_test.go | 4 ++-- .../light-clients/07-tendermint/types/tendermint_test.go | 4 ++-- testing/README.md | 4 ++-- testing/app.go | 4 +++- testing/chain.go | 2 +- testing/coordinator.go | 5 ++--- 25 files changed, 53 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d2f9fd72dc..936201ab6d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### API Breaking +* (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. diff --git a/modules/apps/27-interchain-accounts/controller/ibc_module_test.go b/modules/apps/27-interchain-accounts/controller/ibc_module_test.go index 5137606e764..8939177300e 100644 --- a/modules/apps/27-interchain-accounts/controller/ibc_module_test.go +++ b/modules/apps/27-interchain-accounts/controller/ibc_module_test.go @@ -45,8 +45,8 @@ func TestICATestSuite(t *testing.T) { func (suite *InterchainAccountsTestSuite) SetupTest() { suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) - suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0)) - suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) } func NewICAPath(chainA, chainB *ibctesting.TestChain) *ibctesting.Path { diff --git a/modules/apps/27-interchain-accounts/controller/keeper/keeper_test.go b/modules/apps/27-interchain-accounts/controller/keeper/keeper_test.go index e1678689509..82f43f54caf 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/keeper_test.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/keeper_test.go @@ -38,9 +38,9 @@ type KeeperTestSuite struct { func (suite *KeeperTestSuite) SetupTest() { suite.coordinator = ibctesting.NewCoordinator(suite.T(), 3) - suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0)) - suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) - suite.chainC = suite.coordinator.GetChain(ibctesting.GetChainID(2)) + 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) *ibctesting.Path { diff --git a/modules/apps/27-interchain-accounts/host/ibc_module_test.go b/modules/apps/27-interchain-accounts/host/ibc_module_test.go index 1f76d661a6c..4f51c95965b 100644 --- a/modules/apps/27-interchain-accounts/host/ibc_module_test.go +++ b/modules/apps/27-interchain-accounts/host/ibc_module_test.go @@ -47,8 +47,8 @@ func TestICATestSuite(t *testing.T) { func (suite *InterchainAccountsTestSuite) SetupTest() { suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) - suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0)) - suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) } func NewICAPath(chainA, chainB *ibctesting.TestChain) *ibctesting.Path { diff --git a/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go b/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go index 102cb278ebe..e6781546aed 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go +++ b/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go @@ -38,9 +38,9 @@ type KeeperTestSuite struct { func (suite *KeeperTestSuite) SetupTest() { suite.coordinator = ibctesting.NewCoordinator(suite.T(), 3) - suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0)) - suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) - suite.chainC = suite.coordinator.GetChain(ibctesting.GetChainID(2)) + 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) *ibctesting.Path { diff --git a/modules/apps/27-interchain-accounts/types/account_test.go b/modules/apps/27-interchain-accounts/types/account_test.go index 265a74d09af..0bdeb523e7f 100644 --- a/modules/apps/27-interchain-accounts/types/account_test.go +++ b/modules/apps/27-interchain-accounts/types/account_test.go @@ -33,8 +33,8 @@ type TypesTestSuite struct { func (suite *TypesTestSuite) SetupTest() { suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) - suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0)) - suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) } func TestTypesTestSuite(t *testing.T) { diff --git a/modules/apps/transfer/keeper/keeper_test.go b/modules/apps/transfer/keeper/keeper_test.go index 0fdb1121387..d3a92fdccb0 100644 --- a/modules/apps/transfer/keeper/keeper_test.go +++ b/modules/apps/transfer/keeper/keeper_test.go @@ -27,9 +27,9 @@ type KeeperTestSuite struct { func (suite *KeeperTestSuite) SetupTest() { suite.coordinator = ibctesting.NewCoordinator(suite.T(), 3) - suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0)) - suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) - suite.chainC = suite.coordinator.GetChain(ibctesting.GetChainID(2)) + 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) diff --git a/modules/apps/transfer/transfer_test.go b/modules/apps/transfer/transfer_test.go index fc16aa39a28..73704190208 100644 --- a/modules/apps/transfer/transfer_test.go +++ b/modules/apps/transfer/transfer_test.go @@ -25,9 +25,9 @@ type TransferTestSuite struct { func (suite *TransferTestSuite) SetupTest() { suite.coordinator = ibctesting.NewCoordinator(suite.T(), 3) - suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0)) - suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) - suite.chainC = suite.coordinator.GetChain(ibctesting.GetChainID(2)) + 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 NewTransferPath(chainA, chainB *ibctesting.TestChain) *ibctesting.Path { diff --git a/modules/core/02-client/abci_test.go b/modules/core/02-client/abci_test.go index 84c4f2904ff..43234f22631 100644 --- a/modules/core/02-client/abci_test.go +++ b/modules/core/02-client/abci_test.go @@ -28,8 +28,8 @@ type ClientTestSuite struct { func (suite *ClientTestSuite) SetupTest() { suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) - suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0)) - suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) // set localhost client revision := types.ParseChainID(suite.chainA.GetContext().ChainID()) diff --git a/modules/core/02-client/keeper/keeper_test.go b/modules/core/02-client/keeper/keeper_test.go index 00630f75a2c..fc4fcab053d 100644 --- a/modules/core/02-client/keeper/keeper_test.go +++ b/modules/core/02-client/keeper/keeper_test.go @@ -73,8 +73,8 @@ type KeeperTestSuite struct { func (suite *KeeperTestSuite) SetupTest() { suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) - suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0)) - suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + 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) diff --git a/modules/core/02-client/legacy/v100/store_test.go b/modules/core/02-client/legacy/v100/store_test.go index 4b16dd1f2ac..a1c1be3034a 100644 --- a/modules/core/02-client/legacy/v100/store_test.go +++ b/modules/core/02-client/legacy/v100/store_test.go @@ -32,8 +32,8 @@ func TestLegacyTestSuite(t *testing.T) { // SetupTest creates a coordinator with 2 test chains. func (suite *LegacyTestSuite) SetupTest() { suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) - suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0)) - suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + 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) diff --git a/modules/core/02-client/types/msgs_test.go b/modules/core/02-client/types/msgs_test.go index 2e7631da2de..35dd08aedba 100644 --- a/modules/core/02-client/types/msgs_test.go +++ b/modules/core/02-client/types/msgs_test.go @@ -26,8 +26,8 @@ type TypesTestSuite struct { func (suite *TypesTestSuite) SetupTest() { suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) - suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0)) - suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) } func TestTypesTestSuite(t *testing.T) { diff --git a/modules/core/03-connection/keeper/keeper_test.go b/modules/core/03-connection/keeper/keeper_test.go index 750246e0406..b4dfa839028 100644 --- a/modules/core/03-connection/keeper/keeper_test.go +++ b/modules/core/03-connection/keeper/keeper_test.go @@ -22,8 +22,8 @@ type KeeperTestSuite struct { func (suite *KeeperTestSuite) SetupTest() { suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) - suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0)) - suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) } func TestKeeperTestSuite(t *testing.T) { diff --git a/modules/core/03-connection/types/msgs_test.go b/modules/core/03-connection/types/msgs_test.go index 423762fc888..081e182ce14 100644 --- a/modules/core/03-connection/types/msgs_test.go +++ b/modules/core/03-connection/types/msgs_test.go @@ -42,8 +42,8 @@ type MsgTestSuite struct { func (suite *MsgTestSuite) SetupTest() { suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) - suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0)) - suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) app := simapp.Setup(false) db := dbm.NewMemDB() diff --git a/modules/core/04-channel/keeper/keeper_test.go b/modules/core/04-channel/keeper/keeper_test.go index c37b7744ff3..60888f11c3c 100644 --- a/modules/core/04-channel/keeper/keeper_test.go +++ b/modules/core/04-channel/keeper/keeper_test.go @@ -28,8 +28,8 @@ func TestKeeperTestSuite(t *testing.T) { // SetupTest creates a coordinator with 2 test chains. func (suite *KeeperTestSuite) SetupTest() { suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) - suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0)) - suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + 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) diff --git a/modules/core/ante/ante_test.go b/modules/core/ante/ante_test.go index c1d06e41ca2..e0023448bde 100644 --- a/modules/core/ante/ante_test.go +++ b/modules/core/ante/ante_test.go @@ -27,8 +27,8 @@ type AnteTestSuite struct { // 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(0)) - suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + 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) diff --git a/modules/core/genesis_test.go b/modules/core/genesis_test.go index 8b1e2dd35e2..f66fa9c5f57 100644 --- a/modules/core/genesis_test.go +++ b/modules/core/genesis_test.go @@ -50,8 +50,8 @@ type IBCTestSuite struct { func (suite *IBCTestSuite) SetupTest() { suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) - suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0)) - suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) } func TestIBCTestSuite(t *testing.T) { diff --git a/modules/core/keeper/msg_server_test.go b/modules/core/keeper/msg_server_test.go index 5f94d1bd3d7..bbb80c1c66a 100644 --- a/modules/core/keeper/msg_server_test.go +++ b/modules/core/keeper/msg_server_test.go @@ -38,8 +38,8 @@ type KeeperTestSuite struct { func (suite *KeeperTestSuite) SetupTest() { suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) - suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0)) - suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + 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) diff --git a/modules/core/legacy/v100/genesis_test.go b/modules/core/legacy/v100/genesis_test.go index e06ceba87f7..b0db2e4e1b3 100644 --- a/modules/core/legacy/v100/genesis_test.go +++ b/modules/core/legacy/v100/genesis_test.go @@ -38,8 +38,8 @@ func TestLegacyTestSuite(t *testing.T) { // SetupTest creates a coordinator with 2 test chains. func (suite *LegacyTestSuite) SetupTest() { suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) - suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0)) - suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + 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) diff --git a/modules/light-clients/06-solomachine/types/solomachine_test.go b/modules/light-clients/06-solomachine/types/solomachine_test.go index 8bce55f0412..0a3b5aa5aa8 100644 --- a/modules/light-clients/06-solomachine/types/solomachine_test.go +++ b/modules/light-clients/06-solomachine/types/solomachine_test.go @@ -34,8 +34,8 @@ type SoloMachineTestSuite struct { func (suite *SoloMachineTestSuite) SetupTest() { suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) - suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0)) - suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + 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) suite.solomachineMulti = ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachinemulti", "testing", 4) diff --git a/modules/light-clients/07-tendermint/types/tendermint_test.go b/modules/light-clients/07-tendermint/types/tendermint_test.go index 2cb3647ef6c..37f80f45c1e 100644 --- a/modules/light-clients/07-tendermint/types/tendermint_test.go +++ b/modules/light-clients/07-tendermint/types/tendermint_test.go @@ -57,8 +57,8 @@ type TendermintTestSuite struct { func (suite *TendermintTestSuite) SetupTest() { suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) - suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0)) - suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + 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) diff --git a/testing/README.md b/testing/README.md index 6cf08516b8c..97f540d5c4c 100644 --- a/testing/README.md +++ b/testing/README.md @@ -155,8 +155,8 @@ func TestKeeperTestSuite(t *testing.T) { // 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(0)) // convenience and readability - suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) // convenience and readability + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) // convenience and readability + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) // convenience and readability } ``` diff --git a/testing/app.go b/testing/app.go index 0ce71f43186..1c5bbe7c88a 100644 --- a/testing/app.go +++ b/testing/app.go @@ -58,7 +58,7 @@ func SetupTestingApp() (TestingApp, map[string]json.RawMessage) { // 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(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount, balances ...banktypes.Balance) TestingApp { +func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount, chainID string, balances ...banktypes.Balance) TestingApp { app, genesisState := DefaultTestingAppInit() // set genesis accounts authGenesis := authtypes.NewGenesisState(authtypes.DefaultParams(), genAccs) @@ -117,6 +117,7 @@ func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs // init chain will set the validator set and initialize the genesis accounts app.InitChain( abci.RequestInitChain{ + ChainId: chainID, Validators: []abci.ValidatorUpdate{}, ConsensusParams: simapp.DefaultConsensusParams, AppStateBytes: stateBytes, @@ -126,6 +127,7 @@ func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs // commit genesis changes app.Commit() app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{ + ChainID: chainID, Height: app.LastBlockHeight() + 1, AppHash: app.LastCommitID().Hash, ValidatorsHash: valSet.Hash(), diff --git a/testing/chain.go b/testing/chain.go index 9820c527a4d..281782f7837 100644 --- a/testing/chain.go +++ b/testing/chain.go @@ -90,7 +90,7 @@ func NewTestChain(t *testing.T, coord *Coordinator, chainID string) *TestChain { Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, amount)), } - app := SetupWithGenesisValSet(t, valSet, []authtypes.GenesisAccount{acc}, balance) + app := SetupWithGenesisValSet(t, valSet, []authtypes.GenesisAccount{acc}, chainID, balance) // create current header and call begin block header := tmproto.Header{ diff --git a/testing/coordinator.go b/testing/coordinator.go index c7a5d6edfd1..615bd830ced 100644 --- a/testing/coordinator.go +++ b/testing/coordinator.go @@ -10,9 +10,8 @@ import ( abci "github.com/tendermint/tendermint/abci/types" ) -const ChainIDPrefix = "testchain" - var ( + ChainIDPrefix = "testchain" globalStartTime = time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC) TimeIncrement = time.Second * 5 ) @@ -34,7 +33,7 @@ func NewCoordinator(t *testing.T, n int) *Coordinator { CurrentTime: globalStartTime, } - for i := 0; i < n; i++ { + for i := 1; i <= n; i++ { chainID := GetChainID(i) chains[chainID] = NewTestChain(t, coord, chainID) } From 349bddf3fbf55ff2b5128d2be40c67414ad51797 Mon Sep 17 00:00:00 2001 From: Sean King Date: Mon, 31 Jan 2022 16:01:48 +0100 Subject: [PATCH 006/140] Defensive checks for active channel (#785) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Adding check onChanOpenAck to check for active, open channel * Add defensive check of active channel to OnChanOpenTry * fix: use counterparty.PortId in active channel check * update: change base error for active channel check * comment: add comment explaining overwrite of active channel * fix: change err type * fix: updating to use counterparty PortID * fix: tests * Update modules/apps/27-interchain-accounts/host/keeper/handshake.go Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> (cherry picked from commit 25fb89dce05e658631497889a3485fda8f83644c) --- .../controller/keeper/handshake.go | 7 +++++-- .../controller/keeper/handshake_test.go | 11 +++++++++++ .../host/keeper/genesis_test.go | 2 +- .../host/keeper/handshake.go | 10 +++++++++- .../host/keeper/handshake_test.go | 11 +++++++++++ .../host/keeper/keeper.go | 17 +++++++++++++++++ .../host/keeper/keeper_test.go | 4 ++-- .../apps/27-interchain-accounts/types/errors.go | 15 ++++++++------- 8 files changed, 64 insertions(+), 13 deletions(-) diff --git a/modules/apps/27-interchain-accounts/controller/keeper/handshake.go b/modules/apps/27-interchain-accounts/controller/keeper/handshake.go index 533bb8d89da..5b1fc619f9e 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/handshake.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/handshake.go @@ -9,7 +9,6 @@ import ( icatypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/types" channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" - porttypes "github.com/cosmos/ibc-go/v3/modules/core/05-port/types" ) // OnChanOpenInit performs basic validation of channel initialization. @@ -52,7 +51,7 @@ func (k Keeper) OnChanOpenInit( activeChannelID, found := k.GetOpenActiveChannel(ctx, connectionHops[0], portID) if found { - return sdkerrors.Wrapf(porttypes.ErrInvalidPort, "existing active channel %s for portID %s", activeChannelID, portID) + return sdkerrors.Wrapf(icatypes.ErrActiveChannelAlreadySet, "existing active channel %s for portID %s", activeChannelID, portID) } return nil @@ -79,6 +78,10 @@ func (k Keeper) OnChanOpenAck( return sdkerrors.Wrapf(icatypes.ErrUnknownDataType, "cannot unmarshal ICS-27 interchain accounts metadata") } + if activeChannelID, found := k.GetOpenActiveChannel(ctx, metadata.ControllerConnectionId, portID); found { + return sdkerrors.Wrapf(icatypes.ErrActiveChannelAlreadySet, "existing active channel %s for portID %s", activeChannelID, portID) + } + channel, found := k.channelKeeper.GetChannel(ctx, portID, channelID) if !found { return sdkerrors.Wrapf(channeltypes.ErrChannelNotFound, "failed to retrieve channel %s on port %s", channelID, portID) diff --git a/modules/apps/27-interchain-accounts/controller/keeper/handshake_test.go b/modules/apps/27-interchain-accounts/controller/keeper/handshake_test.go index 3a810a8790a..b08a6913aaf 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/handshake_test.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/handshake_test.go @@ -247,6 +247,17 @@ func (suite *KeeperTestSuite) TestOnChanOpenAck() { }, false, }, + { + "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) + }, false, + }, } for _, tc := range testCases { diff --git a/modules/apps/27-interchain-accounts/host/keeper/genesis_test.go b/modules/apps/27-interchain-accounts/host/keeper/genesis_test.go index 78e447878a7..138d713cf67 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/genesis_test.go +++ b/modules/apps/27-interchain-accounts/host/keeper/genesis_test.go @@ -55,7 +55,7 @@ func (suite *KeeperTestSuite) TestExportGenesis() { genesisState := keeper.ExportGenesis(suite.chainB.GetContext(), suite.chainB.GetSimApp().ICAHostKeeper) suite.Require().Equal(path.EndpointB.ChannelID, genesisState.ActiveChannels[0].ChannelId) - suite.Require().Equal(path.EndpointB.ChannelConfig.PortID, genesisState.ActiveChannels[0].PortId) + suite.Require().Equal(path.EndpointA.ChannelConfig.PortID, genesisState.ActiveChannels[0].PortId) suite.Require().Equal(TestAccAddress.String(), genesisState.InterchainAccounts[0].AccountAddress) suite.Require().Equal(path.EndpointA.ChannelConfig.PortID, genesisState.InterchainAccounts[0].PortId) diff --git a/modules/apps/27-interchain-accounts/host/keeper/handshake.go b/modules/apps/27-interchain-accounts/host/keeper/handshake.go index ac8c863cc19..48b3570dd67 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/handshake.go +++ b/modules/apps/27-interchain-accounts/host/keeper/handshake.go @@ -47,6 +47,10 @@ func (k Keeper) OnChanOpenTry( return "", err } + if activeChannelID, found := k.GetOpenActiveChannel(ctx, connectionHops[0], counterparty.PortId); found { + return "", sdkerrors.Wrapf(icatypes.ErrActiveChannelAlreadySet, "existing active channel %s for portID %s", activeChannelID, portID) + } + // On the host chain the capability may only be claimed during the OnChanOpenTry // The capability being claimed in OpenInit is for a controller chain (the port is different) if err := k.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { @@ -78,7 +82,11 @@ func (k Keeper) OnChanOpenConfirm( return sdkerrors.Wrapf(channeltypes.ErrChannelNotFound, "failed to retrieve channel %s on port %s", channelID, portID) } - k.SetActiveChannelID(ctx, channel.ConnectionHops[0], portID, channelID) + // 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 } diff --git a/modules/apps/27-interchain-accounts/host/keeper/handshake_test.go b/modules/apps/27-interchain-accounts/host/keeper/handshake_test.go index beadf23b674..6f3dc0d25e1 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/handshake_test.go +++ b/modules/apps/27-interchain-accounts/host/keeper/handshake_test.go @@ -111,6 +111,17 @@ func (suite *KeeperTestSuite) TestOnChanOpenTry() { }, false, }, + { + "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.EndpointA.ConnectionID}, ibctesting.DefaultChannelVersion) + suite.chainB.GetSimApp().GetIBCKeeper().ChannelKeeper.SetChannel(suite.chainB.GetContext(), path.EndpointA.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) + }, false, + }, } for _, tc := range testCases { diff --git a/modules/apps/27-interchain-accounts/host/keeper/keeper.go b/modules/apps/27-interchain-accounts/host/keeper/keeper.go index da3c23062c7..598c68789fc 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/keeper.go +++ b/modules/apps/27-interchain-accounts/host/keeper/keeper.go @@ -14,6 +14,7 @@ import ( "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/host/types" icatypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" host "github.com/cosmos/ibc-go/v3/modules/core/24-host" ) @@ -102,6 +103,22 @@ func (k Keeper) GetActiveChannelID(ctx sdk.Context, connectionID, portID string) return string(store.Get(key)), 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) []icatypes.ActiveChannel { store := ctx.KVStore(k.storeKey) diff --git a/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go b/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go index c1a1487335d..2b38f6b5e84 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go +++ b/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go @@ -165,7 +165,7 @@ func (suite *KeeperTestSuite) TestGetAllActiveChannels() { expectedChannels := []icatypes.ActiveChannel{ { ConnectionId: ibctesting.FirstConnectionID, - PortId: path.EndpointB.ChannelConfig.PortID, + PortId: path.EndpointA.ChannelConfig.PortID, ChannelId: path.EndpointB.ChannelID, }, { @@ -223,7 +223,7 @@ func (suite *KeeperTestSuite) TestIsActiveChannel() { err := SetupICAPath(path, TestOwnerAddress) suite.Require().NoError(err) - isActive := suite.chainB.GetSimApp().ICAHostKeeper.IsActiveChannel(suite.chainB.GetContext(), ibctesting.FirstConnectionID, path.EndpointB.ChannelConfig.PortID) + isActive := suite.chainB.GetSimApp().ICAHostKeeper.IsActiveChannel(suite.chainB.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID) suite.Require().True(isActive) } diff --git a/modules/apps/27-interchain-accounts/types/errors.go b/modules/apps/27-interchain-accounts/types/errors.go index e0a5c141de9..6ce9f2a9dea 100644 --- a/modules/apps/27-interchain-accounts/types/errors.go +++ b/modules/apps/27-interchain-accounts/types/errors.go @@ -14,11 +14,12 @@ var ( ErrInterchainAccountNotFound = sdkerrors.Register(ModuleName, 8, "interchain account not found") ErrInterchainAccountAlreadySet = sdkerrors.Register(ModuleName, 9, "interchain account is already set") ErrActiveChannelNotFound = sdkerrors.Register(ModuleName, 10, "no active channel for this owner") - ErrInvalidVersion = sdkerrors.Register(ModuleName, 11, "invalid interchain accounts version") - ErrInvalidAccountAddress = sdkerrors.Register(ModuleName, 12, "invalid account address") - ErrUnsupported = sdkerrors.Register(ModuleName, 13, "interchain account does not support this action") - ErrInvalidControllerPort = sdkerrors.Register(ModuleName, 14, "invalid controller port") - ErrInvalidHostPort = sdkerrors.Register(ModuleName, 15, "invalid host port") - ErrInvalidTimeoutTimestamp = sdkerrors.Register(ModuleName, 16, "timeout timestamp must be in the future") - ErrInvalidCodec = sdkerrors.Register(ModuleName, 17, "codec is not supported") + ErrActiveChannelAlreadySet = sdkerrors.Register(ModuleName, 11, "active channel already set for this owner") + ErrInvalidVersion = sdkerrors.Register(ModuleName, 12, "invalid interchain accounts version") + ErrInvalidAccountAddress = sdkerrors.Register(ModuleName, 13, "invalid account address") + ErrUnsupported = sdkerrors.Register(ModuleName, 14, "interchain account does not support this action") + ErrInvalidControllerPort = sdkerrors.Register(ModuleName, 15, "invalid controller port") + ErrInvalidHostPort = sdkerrors.Register(ModuleName, 16, "invalid host port") + ErrInvalidTimeoutTimestamp = sdkerrors.Register(ModuleName, 17, "timeout timestamp must be in the future") + ErrInvalidCodec = sdkerrors.Register(ModuleName, 18, "codec is not supported") ) From f0ea30ade1fa3ff2801e297aaf568a341fea309a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 2 Feb 2022 13:37:17 +0100 Subject: [PATCH 007/140] build(deps): bump github.com/cosmos/cosmos-sdk from 0.44.5 to 0.45.0 (#769) (#825) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [github.com/cosmos/cosmos-sdk](https://github.com/cosmos/cosmos-sdk) from 0.44.5 to 0.45.0. Closes: #734 Closes: #763
Release notes

Sourced from github.com/cosmos/cosmos-sdk's releases.

Cosmos SDK v0.45.0 is a logical continuation of the v0.44.* series, but brings a couple of state- and API-breaking changes requested by the community.

State-Breaking Changes

There are few important changes in gas consumption, which improve the gas economics:

  • We now charge gas in two new places: on .Seek() even if there are no entries, and for the key length (on top of the value length).
  • When block gas limit is exceeded, we consume the maximum gas possible (to charge for the performed computation). We also fixed the bug when the last transaction in a block exceeds the block gas limit, it returns an error result, but the tx is actually committed successfully.

Finally, a small improvement in gov, we increased the maximum proposal description size from 5k characters to 10k characters.

API-Breaking Changes

  • The BankKeeper interface has a new HasSupply method to ensure that input denom actually exists on chain.
  • The CommitMultiStore interface contains a new SetIAVLCacheSize method for a configurable IAVL cache size.
  • AuthKeeper interface in x/auth now includes a function HasAccount.
  • Moved TestMnemonic from testutil package to testdata.

Finally, when using the SetOrder* functions in simapp, e.g. SetOrderBeginBlocker, we now require that all modules be present in the function arguments, or else the node panics at startup. We also added a new SetOrderMigration function to set the order of running module migrations.

Improvements

  • Speedup improvements (e.g. speedup iterator creation after delete heavy workloads, lower allocations for Coins.String(), reduce RAM/CPU usage inside store/cachekv's Store.Write) are included in this release.
  • Upgrade Rosetta to v0.7.0 .
  • Support in-place migration ordering.
  • Copied and updated server.GenerateCoinKey and server.GenerateServerCoinKey functions to the testutil package. These functions in server package are marked deprecated and will be removed in the next release. In the testutil.GenerateServerCoinKey version we added support for custom mnemonics in in-process testing network.

See our CHANGELOG for the exhaustive list of all changes, or a full commit diff.

Cosmos SDK v0.45.0 Release Candidate 1

Release Notes

Cosmos SDK v0.45.0 is a logical continuation of the v0.44.* series, but brings a couple of state- and API-breaking changes requested by the community.

State-Breaking Changes

There are few important changes in gas consumption, which improve the gas economics:

  • We now charge gas in two new places: on .Seek() even if there are no entries, and for the key length (on top of the value length).
  • When block gas limit is exceeded, we consume the maximum gas possible (to charge for the performed computation). We also fixed the bug when the last transaction in a block exceeds the block gas limit, it returns an error result, but the tx is actually committed successfully.

Finally, a small improvement in gov, we increased the maximum proposal description size from 5k characters to 10k characters.

API-Breaking Changes

  • The BankKeeper interface has a new HasSupply method to ensure that input denom actually exists on chain.
  • The CommitMultiStore interface contains a new SetIAVLCacheSize method for a configurable IAVL cache size.
  • AuthKeeper interface in x/auth now includes a function HasAccount.

... (truncated)

Changelog

Sourced from github.com/cosmos/cosmos-sdk's changelog.

v0.45.0 - 2022-01-18

State Machine Breaking

  • #10833 fix reported tx gas used when block gas limit exceeded.
  • (auth) #10536 Enable SetSequence for ModuleAccount.
  • (store) #10218 Charge gas even when there are no entries while seeking.
  • (store) #10247 Charge gas for the key length in gas meter.
  • (x/gov) #10740 Increase maximum proposal description size from 5k characters to 10k characters.
  • #10814 revert tx when block gas limit exceeded.

API Breaking Changes

  • #10561 The CommitMultiStore interface contains a new SetIAVLCacheSize method
  • #10922, [/#10956](cosmos/cosmos-sdk#10956) Deprecate key server.Generate* functions and move them to testutil and support custom mnemonics in in-process testing network. Moved TestMnemonic from testutil package to testdata.

Features

  • #10614 Support in-place migration ordering

Improvements

  • #10486 store/cachekv's Store.Write conservatively looks up keys, but also uses the map clearing idiom to reduce the RAM usage, CPU time usage, and garbage collection pressure from clearing maps, instead of allocating new maps.
  • (store) #10741 Significantly speedup iterator creation after delete heavy workloads. Significantly improves IBC migration times.
  • (module) #10711 Panic at startup if the app developer forgot to add modules in the SetOrder{BeginBlocker, EndBlocker, InitGenesis, ExportGenesis} functions. This means that all modules, even those who have empty implementations for those methods, need to be added to SetOrder*.
  • (types) #10076 Significantly speedup and lower allocations for Coins.String().
  • (auth) #10022 AuthKeeper interface in x/auth now includes a function HasAccount.
  • #10393 Add HasSupply method to bank keeper to ensure that input denom actually exists on chain.

Bug Fixes

  • (std/codec) [/#10595](cosmos/cosmos-sdk#10595) Add evidence to std/codec to be able to decode evidence in client interactions.
  • (types) #9627 Fix nil pointer panic on NewBigIntFromInt.
  • #10725 populate ctx.ConsensusParams for begin/end blockers.
  • #9829 Fixed Coin denom sorting not being checked during Balance.Validate check. Refactored the Validation logic to use Coins.Validate for Balance.Coins
  • #10061 and #10515 Ensure that LegacyAminoPubKey struct correctly unmarshals from JSON
Commits
  • b6c77e6 chore: release v0.45 changelog (#10964)
  • 8236b26 chore: move server.GenerateCoinKey and server.GenerateSaveCoinKey to testutil...
  • 90ffbce feat: support custom mnemonics in in-process testing network (backport #10922...
  • c1c1ad7 chore: v0.45.0 Release Notes (#10760)
  • ba1e099 fix: revert tx when block gas limit exceeded (backport: #10770) (#10814)
  • a5c60b7 feat!: x/gov: raise max description length to 10k chars (backport #10740) (#1...
  • 05656a2 fix: use full gas on overflow (backport #10897) (#10912)
  • 8932338 feat: support in-place migration ordering (backport #10614) (#10890)
  • 6d44d71 fix!: tx result don't report block gas used as tx gas used (#10833)
  • 71a168d fix: recreate compat field, of null pubkeys in multisig (backport #10515) (#1...
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/cosmos/cosmos-sdk&package-manager=go_modules&previous-version=0.44.5&new-version=0.45.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
(cherry picked from commit f7bb1427bf5d8c7a52b9db43efd16657112936df) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- testing/simapp/app.go | 14 +++++++++++--- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index e6b7a163079..cb79c90045a 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alp require ( github.com/armon/go-metrics v0.3.10 github.com/confio/ics23/go v0.6.6 - github.com/cosmos/cosmos-sdk v0.44.5 + github.com/cosmos/cosmos-sdk v0.45.0 github.com/gogo/protobuf v1.3.3 github.com/golang/protobuf v1.5.2 github.com/gorilla/mux v1.8.0 @@ -37,7 +37,7 @@ require ( github.com/btcsuite/btcd v0.22.0-beta // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect - github.com/coinbase/rosetta-sdk-go v0.6.10 // indirect + github.com/coinbase/rosetta-sdk-go v0.7.0 // indirect github.com/cosmos/btcutil v1.0.4 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/iavl v0.17.3 // indirect diff --git a/go.sum b/go.sum index 10a1caeec98..aeda86c4ec6 100644 --- a/go.sum +++ b/go.sum @@ -182,8 +182,8 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/coinbase/rosetta-sdk-go v0.6.10 h1:rgHD/nHjxLh0lMEdfGDqpTtlvtSBwULqrrZ2qPdNaCM= -github.com/coinbase/rosetta-sdk-go v0.6.10/go.mod h1:J/JFMsfcePrjJZkwQFLh+hJErkAmdm9Iyy3D5Y0LfXo= +github.com/coinbase/rosetta-sdk-go v0.7.0 h1:lmTO/JEpCvZgpbkOITL95rA80CPKb5CtMzLaqF2mCNg= +github.com/coinbase/rosetta-sdk-go v0.7.0/go.mod h1:7nD3oBPIiHqhRprqvMgPoGxe/nyq3yftRmpsy29coWE= github.com/confio/ics23/go v0.6.6 h1:pkOy18YxxJ/r0XFDCnrl4Bjv6h4LkBSpLS6F38mrKL8= github.com/confio/ics23/go v0.6.6/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= @@ -202,8 +202,8 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cosmos/btcutil v1.0.4 h1:n7C2ngKXo7UC9gNyMNLbzqz7Asuf+7Qv4gnX/rOdQ44= github.com/cosmos/btcutil v1.0.4/go.mod h1:Ffqc8Hn6TJUdDgHBwIZLtrLQC1KdJ9jGJl/TvgUaxbU= -github.com/cosmos/cosmos-sdk v0.44.5 h1:t5h+KPzZb0Zsag1RP1DCMQlyJyIQqJcqSPJrbUCDGHY= -github.com/cosmos/cosmos-sdk v0.44.5/go.mod h1:maUA6m2TBxOJZkbwl0eRtEBgTX37kcaiOWU5t1HEGaY= +github.com/cosmos/cosmos-sdk v0.45.0 h1:DHD+CIRZ+cYgiLXuTEUL/aprnfPsWSwaww/fIZEsZlk= +github.com/cosmos/cosmos-sdk v0.45.0/go.mod h1:XXS/asyCqWNWkx2rW6pSuen+EVcpAFxq6khrhnZgHaQ= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= diff --git a/testing/simapp/app.go b/testing/simapp/app.go index 0287dc0495b..7f361aa9b8a 100644 --- a/testing/simapp/app.go +++ b/testing/simapp/app.go @@ -36,6 +36,7 @@ import ( 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" @@ -431,9 +432,16 @@ func NewSimApp( // NOTE: capability module's beginblocker must come before any modules using capabilities (e.g. IBC) app.mm.SetOrderBeginBlockers( upgradetypes.ModuleName, capabilitytypes.ModuleName, minttypes.ModuleName, distrtypes.ModuleName, slashingtypes.ModuleName, - evidencetypes.ModuleName, stakingtypes.ModuleName, ibchost.ModuleName, + evidencetypes.ModuleName, stakingtypes.ModuleName, ibchost.ModuleName, ibctransfertypes.ModuleName, authtypes.ModuleName, + banktypes.ModuleName, govtypes.ModuleName, crisistypes.ModuleName, genutiltypes.ModuleName, authz.ModuleName, feegrant.ModuleName, + paramstypes.ModuleName, vestingtypes.ModuleName, icatypes.ModuleName, ibcmock.ModuleName, + ) + app.mm.SetOrderEndBlockers( + crisistypes.ModuleName, govtypes.ModuleName, stakingtypes.ModuleName, ibchost.ModuleName, ibctransfertypes.ModuleName, + capabilitytypes.ModuleName, authtypes.ModuleName, banktypes.ModuleName, distrtypes.ModuleName, slashingtypes.ModuleName, + minttypes.ModuleName, genutiltypes.ModuleName, evidencetypes.ModuleName, authz.ModuleName, feegrant.ModuleName, paramstypes.ModuleName, + upgradetypes.ModuleName, vestingtypes.ModuleName, icatypes.ModuleName, ibcmock.ModuleName, ) - app.mm.SetOrderEndBlockers(crisistypes.ModuleName, govtypes.ModuleName, stakingtypes.ModuleName) // NOTE: The genutils module must occur after staking so that pools are // properly initialized with tokens from genesis accounts. @@ -444,7 +452,7 @@ func NewSimApp( capabilitytypes.ModuleName, authtypes.ModuleName, banktypes.ModuleName, distrtypes.ModuleName, stakingtypes.ModuleName, slashingtypes.ModuleName, govtypes.ModuleName, minttypes.ModuleName, crisistypes.ModuleName, ibchost.ModuleName, genutiltypes.ModuleName, evidencetypes.ModuleName, authz.ModuleName, ibctransfertypes.ModuleName, - icatypes.ModuleName, ibcmock.ModuleName, feegrant.ModuleName, + icatypes.ModuleName, ibcmock.ModuleName, feegrant.ModuleName, paramstypes.ModuleName, upgradetypes.ModuleName, vestingtypes.ModuleName, ) app.mm.RegisterInvariants(&app.CrisisKeeper) From c230603562d50fde87b78199a458148afab4f206 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 2 Feb 2022 14:08:02 +0100 Subject: [PATCH 008/140] rename portid and port prefix for interchain accounts submodules (#779) (#826) ## Description I decided to remove the `-` from the port ID (`ica-host`) and prefix (`ica-controller`) and just make it one word (`icahost` and `icacontroller`), just in case we decide to do some parsing based on a `-` delimiter in the future. closes: #778 --- 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. - [ ] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [ ] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit 8cba0ebd4f6a6ff2017433950dce3a29b8394882) Co-authored-by: Carlos Rodriguez --- modules/apps/27-interchain-accounts/types/keys.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/apps/27-interchain-accounts/types/keys.go b/modules/apps/27-interchain-accounts/types/keys.go index d120fe6ac7c..581aea8ae37 100644 --- a/modules/apps/27-interchain-accounts/types/keys.go +++ b/modules/apps/27-interchain-accounts/types/keys.go @@ -9,10 +9,10 @@ const ( ModuleName = "interchainaccounts" // PortID is the default port id that the interchain accounts host submodule binds to - PortID = "interchain-account" + PortID = "icahost" // PortPrefix is the default port prefix that the interchain accounts controller submodule binds to - PortPrefix = "ics27-" + PortPrefix = "icacontroller-" // Version defines the current version for interchain accounts Version = "ics27-1" From dea05056bb03a7ea23f8e082dfbfe2aae77bcdc2 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 2 Feb 2022 14:09:37 +0100 Subject: [PATCH 009/140] test: adding ica test for multiple controllers, single host (#816) (#829) * adding test for multiple controllers, single host * updating inline comments * updating todos with correct ports * reorder to use pathCToB.Endpoint for connection IDs (cherry picked from commit 83c3e4160ba0399e04397ddf09f42a73259d7f56) Co-authored-by: Damian Nolan --- .../controller/ibc_module_test.go | 70 ++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/modules/apps/27-interchain-accounts/controller/ibc_module_test.go b/modules/apps/27-interchain-accounts/controller/ibc_module_test.go index ca1b0db6b06..9fd7486cf1a 100644 --- a/modules/apps/27-interchain-accounts/controller/ibc_module_test.go +++ b/modules/apps/27-interchain-accounts/controller/ibc_module_test.go @@ -46,6 +46,7 @@ type InterchainAccountsTestSuite struct { // testing chains used for convenience and readability chainA *ibctesting.TestChain chainB *ibctesting.TestChain + chainC *ibctesting.TestChain } func TestICATestSuite(t *testing.T) { @@ -53,9 +54,10 @@ func TestICATestSuite(t *testing.T) { } func (suite *InterchainAccountsTestSuite) SetupTest() { - suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) + 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) *ibctesting.Path { @@ -89,6 +91,7 @@ func RegisterInterchainAccount(endpoint *ibctesting.Endpoint, owner string) erro // update port/channel ids endpoint.ChannelID = channeltypes.FormatChannelIdentifier(channelSequence) endpoint.ChannelConfig.PortID = portID + endpoint.ChannelConfig.Version = TestVersion return nil } @@ -632,3 +635,68 @@ func (suite *InterchainAccountsTestSuite) TestOnTimeoutPacket() { }) } } + +func (suite *InterchainAccountsTestSuite) TestSingleHostMultipleControllers() { + var ( + pathAToB *ibctesting.Path + pathCToB *ibctesting.Path + ) + + testCases := []struct { + msg string + malleate func() + expPass bool + }{ + { + "success", + func() {}, + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.msg, func() { + suite.SetupTest() // reset + + // Setup a new path from A(controller) -> B(host) + pathAToB = NewICAPath(suite.chainA, suite.chainB) + suite.coordinator.SetupConnections(pathAToB) + + err := SetupICAPath(pathAToB, TestOwnerAddress) + suite.Require().NoError(err) + + // Setup a new path from C(controller) -> B(host) + pathCToB = NewICAPath(suite.chainC, suite.chainB) + suite.coordinator.SetupConnections(pathCToB) + + // 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, + })) + + 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) + }) + } +} From 60fdc75e2a73ffab520f0f642a19880ca8788d43 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 2 Feb 2022 14:50:25 +0100 Subject: [PATCH 010/140] the ica_auth page was renamed to auth-modules (#792) (#830) ## Description This [link](https://ibc.cosmos.network/main/app_modules/interchain-accounts/ica_auth.html) was not working because the page was renamed but not removed from the vuepress config. image 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. - [x] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [x] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit 142056fce088a7893b30edaf34a576ff73b6f5b2) Co-authored-by: Carlos Rodriguez --- docs/.vuepress/config.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index d78f801654c..8a69e377fe3 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -141,11 +141,6 @@ module.exports = { directory: false, path: "/app-modules/interchain-accounts/transactions.html" }, - { - title: "Authentication module development", - directory: false, - path: "/app_modules/interchain-accounts/ica_auth.html" - }, ] }, ] From 3f882c76fdb6f01bc56b51b1b5f0fcaeaf16e9a9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 2 Feb 2022 15:18:28 +0100 Subject: [PATCH 011/140] chore: replace error string in transfer acks with const (#818) (#831) * fix: adding ack error string const for transfer * updating godoc * adding warning note to godoc in 04-channel * updating to include abci error code, and copy tests from ica * adding changelog entry (cherry picked from commit ac46ac06084f586a460b092b2b293a321b7c43d6) Co-authored-by: Damian Nolan --- CHANGELOG.md | 2 + .../27-interchain-accounts/host/types/ack.go | 4 +- modules/apps/transfer/ibc_module.go | 2 +- modules/apps/transfer/types/ack.go | 27 +++++ modules/apps/transfer/types/ack_test.go | 101 ++++++++++++++++++ .../core/04-channel/types/acknowledgement.go | 2 + 6 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 modules/apps/transfer/types/ack.go create mode 100644 modules/apps/transfer/types/ack_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 95c129cb08b..df182d73b6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,8 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### 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 * (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. diff --git a/modules/apps/27-interchain-accounts/host/types/ack.go b/modules/apps/27-interchain-accounts/host/types/ack.go index 047a7063e46..202404fff3a 100644 --- a/modules/apps/27-interchain-accounts/host/types/ack.go +++ b/modules/apps/27-interchain-accounts/host/types/ack.go @@ -14,12 +14,12 @@ const ( ackErrorString = "error handling packet on host chain: see events for details" ) -// AcknowledgementErrorString returns a deterministic error string which may be used in +// NewErrorAcknowledgement returns a deterministic error string which may be used in // the packet acknowledgement. func NewErrorAcknowledgement(err error) channeltypes.Acknowledgement { // the ABCI code is included in the abcitypes.ResponseDeliverTx hash // constructed in Tendermint and is therefore determinstic - _, code, _ := sdkerrors.ABCIInfo(err, false) // discard non-determinstic codespace and log values + _, code, _ := sdkerrors.ABCIInfo(err, false) // discard non-deterministic codespace and log values errorString := fmt.Sprintf("ABCI code: %d: %s", code, ackErrorString) diff --git a/modules/apps/transfer/ibc_module.go b/modules/apps/transfer/ibc_module.go index 1ad67d16a85..26f1c533434 100644 --- a/modules/apps/transfer/ibc_module.go +++ b/modules/apps/transfer/ibc_module.go @@ -181,7 +181,7 @@ func (im IBCModule) OnRecvPacket( if ack.Success() { err := im.keeper.OnRecvPacket(ctx, packet, data) if err != nil { - ack = channeltypes.NewErrorAcknowledgement(err.Error()) + ack = types.NewErrorAcknowledgement(err) } } diff --git a/modules/apps/transfer/types/ack.go b/modules/apps/transfer/types/ack.go new file mode 100644 index 00000000000..6512f2e8371 --- /dev/null +++ b/modules/apps/transfer/types/ack.go @@ -0,0 +1,27 @@ +package types + +import ( + "fmt" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" +) + +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 on destination chain: see events for details" +) + +// NewErrorAcknowledgement returns a deterministic error string which may be used in +// the packet acknowledgement. +func NewErrorAcknowledgement(err error) channeltypes.Acknowledgement { + // the ABCI code is included in the abcitypes.ResponseDeliverTx hash + // constructed in Tendermint and is therefore deterministic + _, code, _ := sdkerrors.ABCIInfo(err, false) // discard non-determinstic codespace and log values + + errorString := fmt.Sprintf("ABCI code: %d: %s", code, ackErrorString) + + return channeltypes.NewErrorAcknowledgement(errorString) +} diff --git a/modules/apps/transfer/types/ack_test.go b/modules/apps/transfer/types/ack_test.go new file mode 100644 index 00000000000..bc4e2d07afc --- /dev/null +++ b/modules/apps/transfer/types/ack_test.go @@ -0,0 +1,101 @@ +package types_test + +import ( + "testing" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/stretchr/testify/suite" + abcitypes "github.com/tendermint/tendermint/abci/types" + tmprotostate "github.com/tendermint/tendermint/proto/tendermint/state" + tmstate "github.com/tendermint/tendermint/state" + + "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/host/types" + ibctesting "github.com/cosmos/ibc-go/v3/testing" +) + +const ( + gasUsed = uint64(100) + gasWanted = uint64(100) +) + +type TypesTestSuite struct { + suite.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) { + suite.Run(t, new(TypesTestSuite)) +} + +// The safety of including ABCI error codes in the acknowledgement rests +// on the inclusion of these ABCI error codes in the abcitypes.ResposneDeliverTx +// 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 := sdkerrors.Wrap(sdkerrors.ErrOutOfGas, "error string 1") + errSameABCICode := sdkerrors.Wrap(sdkerrors.ErrOutOfGas, "error string 2") + + // different ABCI error code used + errDifferentABCICode := sdkerrors.ErrNotFound + + deliverTx := sdkerrors.ResponseDeliverTx(err, gasUsed, gasWanted, false) + responses := tmprotostate.ABCIResponses{ + DeliverTxs: []*abcitypes.ResponseDeliverTx{ + &deliverTx, + }, + } + + deliverTxSameABCICode := sdkerrors.ResponseDeliverTx(errSameABCICode, gasUsed, gasWanted, false) + responsesSameABCICode := tmprotostate.ABCIResponses{ + DeliverTxs: []*abcitypes.ResponseDeliverTx{ + &deliverTxSameABCICode, + }, + } + + deliverTxDifferentABCICode := sdkerrors.ResponseDeliverTx(errDifferentABCICode, gasUsed, gasWanted, false) + responsesDifferentABCICode := tmprotostate.ABCIResponses{ + DeliverTxs: []*abcitypes.ResponseDeliverTx{ + &deliverTxDifferentABCICode, + }, + } + + hash := tmstate.ABCIResponsesResultsHash(&responses) + hashSameABCICode := tmstate.ABCIResponsesResultsHash(&responsesSameABCICode) + hashDifferentABCICode := tmstate.ABCIResponsesResultsHash(&responsesDifferentABCICode) + + 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 := sdkerrors.Wrap(sdkerrors.ErrOutOfGas, "error string 1") + errSameABCICode := sdkerrors.Wrap(sdkerrors.ErrOutOfGas, "error string 2") + + // different ABCI error code used + errDifferentABCICode := sdkerrors.ErrNotFound + + ack := types.NewErrorAcknowledgement(err) + ackSameABCICode := types.NewErrorAcknowledgement(errSameABCICode) + ackDifferentABCICode := types.NewErrorAcknowledgement(errDifferentABCICode) + + suite.Require().Equal(ack, ackSameABCICode) + suite.Require().NotEqual(ack, ackDifferentABCICode) + +} diff --git a/modules/core/04-channel/types/acknowledgement.go b/modules/core/04-channel/types/acknowledgement.go index cfc088ab0c9..b46de2b981d 100644 --- a/modules/core/04-channel/types/acknowledgement.go +++ b/modules/core/04-channel/types/acknowledgement.go @@ -20,6 +20,8 @@ func NewResultAcknowledgement(result []byte) Acknowledgement { // 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 string) Acknowledgement { return Acknowledgement{ Response: &Acknowledgement_Error{ From 5d9e7db69913513f25ed88ecf3d44f17e767a2e9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 2 Feb 2022 16:11:58 +0100 Subject: [PATCH 012/140] refactor: active channel key format (#823) (#832) ## Description Updating the store key format for active channels based on @AdityaSripal comment. closes: [Adityas comment](https://github.com/cosmos/ibc-go/pull/814#pullrequestreview-870330601) --- 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. - [ ] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [ ] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit 4c28c1ca7503a9c0603202560097fadce70d4cf2) Co-authored-by: Sean King --- .../27-interchain-accounts/controller/keeper/keeper.go | 8 ++++---- modules/apps/27-interchain-accounts/host/keeper/keeper.go | 8 ++++---- modules/apps/27-interchain-accounts/types/keys.go | 4 ++-- modules/apps/27-interchain-accounts/types/keys_test.go | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/modules/apps/27-interchain-accounts/controller/keeper/keeper.go b/modules/apps/27-interchain-accounts/controller/keeper/keeper.go index fc643cea30d..7216ccb7fa8 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/keeper.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/keeper.go @@ -105,7 +105,7 @@ func (k Keeper) ClaimCapability(ctx sdk.Context, cap *capabilitytypes.Capability // 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 := ctx.KVStore(k.storeKey) - key := icatypes.KeyActiveChannel(connectionID, portID) + key := icatypes.KeyActiveChannel(portID, connectionID) if !store.Has(key) { return "", false @@ -141,8 +141,8 @@ func (k Keeper) GetAllActiveChannels(ctx sdk.Context) []icatypes.ActiveChannel { keySplit := strings.Split(string(iterator.Key()), "/") ch := icatypes.ActiveChannel{ - ConnectionId: keySplit[1], - PortId: keySplit[2], + ConnectionId: keySplit[2], + PortId: keySplit[1], ChannelId: string(iterator.Value()), } @@ -155,7 +155,7 @@ func (k Keeper) GetAllActiveChannels(ctx sdk.Context) []icatypes.ActiveChannel { // SetActiveChannelID stores the active channelID, keyed by the provided connectionID and portID func (k Keeper) SetActiveChannelID(ctx sdk.Context, connectionID, portID, channelID string) { store := ctx.KVStore(k.storeKey) - store.Set(icatypes.KeyActiveChannel(connectionID, portID), []byte(channelID)) + store.Set(icatypes.KeyActiveChannel(portID, connectionID), []byte(channelID)) } // IsActiveChannel returns true if there exists an active channel for the provided connectionID and portID, otherwise false diff --git a/modules/apps/27-interchain-accounts/host/keeper/keeper.go b/modules/apps/27-interchain-accounts/host/keeper/keeper.go index 598c68789fc..181153a0fb5 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/keeper.go +++ b/modules/apps/27-interchain-accounts/host/keeper/keeper.go @@ -94,7 +94,7 @@ func (k Keeper) ClaimCapability(ctx sdk.Context, cap *capabilitytypes.Capability // 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 := ctx.KVStore(k.storeKey) - key := icatypes.KeyActiveChannel(connectionID, portID) + key := icatypes.KeyActiveChannel(portID, connectionID) if !store.Has(key) { return "", false @@ -130,8 +130,8 @@ func (k Keeper) GetAllActiveChannels(ctx sdk.Context) []icatypes.ActiveChannel { keySplit := strings.Split(string(iterator.Key()), "/") ch := icatypes.ActiveChannel{ - ConnectionId: keySplit[1], - PortId: keySplit[2], + ConnectionId: keySplit[2], + PortId: keySplit[1], ChannelId: string(iterator.Value()), } @@ -144,7 +144,7 @@ func (k Keeper) GetAllActiveChannels(ctx sdk.Context) []icatypes.ActiveChannel { // SetActiveChannelID stores the active channelID, keyed by the provided connectionID and portID func (k Keeper) SetActiveChannelID(ctx sdk.Context, connectionID, portID, channelID string) { store := ctx.KVStore(k.storeKey) - store.Set(icatypes.KeyActiveChannel(connectionID, portID), []byte(channelID)) + store.Set(icatypes.KeyActiveChannel(portID, connectionID), []byte(channelID)) } // IsActiveChannel returns true if there exists an active channel for the provided connectionID and portID, otherwise false diff --git a/modules/apps/27-interchain-accounts/types/keys.go b/modules/apps/27-interchain-accounts/types/keys.go index 581aea8ae37..c2bde682551 100644 --- a/modules/apps/27-interchain-accounts/types/keys.go +++ b/modules/apps/27-interchain-accounts/types/keys.go @@ -39,8 +39,8 @@ var ( ) // KeyActiveChannel creates and returns a new key used for active channels store operations -func KeyActiveChannel(connectionID, portID string) []byte { - return []byte(fmt.Sprintf("%s/%s/%s", ActiveChannelKeyPrefix, connectionID, portID)) +func KeyActiveChannel(portID, connectionID string) []byte { + return []byte(fmt.Sprintf("%s/%s/%s", ActiveChannelKeyPrefix, portID, connectionID)) } // KeyOwnerAccount creates and returns a new key used for interchain account store operations diff --git a/modules/apps/27-interchain-accounts/types/keys_test.go b/modules/apps/27-interchain-accounts/types/keys_test.go index 4fe7b5a813f..02da485bf32 100644 --- a/modules/apps/27-interchain-accounts/types/keys_test.go +++ b/modules/apps/27-interchain-accounts/types/keys_test.go @@ -5,8 +5,8 @@ import ( ) func (suite *TypesTestSuite) TestKeyActiveChannel() { - key := types.KeyActiveChannel("connection-id", "port-id") - suite.Require().Equal("activeChannel/connection-id/port-id", string(key)) + key := types.KeyActiveChannel("port-id", "connection-id") + suite.Require().Equal("activeChannel/port-id/connection-id", string(key)) } func (suite *TypesTestSuite) TestKeyOwnerAccount() { From 6d9545600991f6eaae1bb922dd78c183cb874ed6 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 2 Feb 2022 16:19:41 +0100 Subject: [PATCH 013/140] refactor: RegisterInterchainAccount (#814) (#835) ## Description closes: #802 --- 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. - [ ] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [ ] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit fed6a86e607aa8e0f5c7ad6cb685476f78dde87f) Co-authored-by: Sean King --- .../controller/keeper/account.go | 17 ++++++++++++----- .../controller/keeper/account_test.go | 9 +++++---- .../apps/27-interchain-accounts/types/errors.go | 4 ++-- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/modules/apps/27-interchain-accounts/controller/keeper/account.go b/modules/apps/27-interchain-accounts/controller/keeper/account.go index aacbe751c6b..143c3a6055e 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/account.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/account.go @@ -21,13 +21,20 @@ func (k Keeper) RegisterInterchainAccount(ctx sdk.Context, connectionID, owner s return err } - if k.portKeeper.IsBound(ctx, portID) { - return sdkerrors.Wrap(icatypes.ErrPortAlreadyBound, portID) + // if there is an active channel for this portID / connectionID return an error + activeChannelID, found := k.GetOpenActiveChannel(ctx, connectionID, portID) + if found { + return sdkerrors.Wrapf(icatypes.ErrActiveChannelAlreadySet, "existing active channel %s for portID %s on connection %s for owner %s", activeChannelID, portID, connectionID, owner) } - cap := k.BindPort(ctx, portID) - if err := k.ClaimCapability(ctx, cap, host.PortPath(portID)); err != nil { - return sdkerrors.Wrap(err, "unable to bind to newly generated portID") + switch { + case k.portKeeper.IsBound(ctx, portID) && !k.IsBound(ctx, portID): + return sdkerrors.Wrapf(icatypes.ErrPortAlreadyBound, "another module has claimed capability for and bound port with portID: %s", portID) + case !k.portKeeper.IsBound(ctx, portID): + cap := k.BindPort(ctx, portID) + if err := k.ClaimCapability(ctx, cap, host.PortPath(portID)); err != nil { + return sdkerrors.Wrapf(err, "unable to bind to newly generated portID: %s", portID) + } } connectionEnd, err := k.channelKeeper.GetConnection(ctx, connectionID) diff --git a/modules/apps/27-interchain-accounts/controller/keeper/account_test.go b/modules/apps/27-interchain-accounts/controller/keeper/account_test.go index 8086726bdc2..11334c332ea 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/account_test.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/account_test.go @@ -3,6 +3,7 @@ package keeper_test import ( icatypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/types" channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v3/modules/core/24-host" ibctesting "github.com/cosmos/ibc-go/v3/testing" ) @@ -22,9 +23,11 @@ func (suite *KeeperTestSuite) TestRegisterInterchainAccount() { "success", func() {}, true, }, { - "port is already bound", + "port is already bound for owner but capability is claimed by another module", func() { - suite.chainA.GetSimApp().IBCKeeper.PortKeeper.BindPort(suite.chainA.GetContext(), TestPortID) + cap := suite.chainA.GetSimApp().IBCKeeper.PortKeeper.BindPort(suite.chainA.GetContext(), TestPortID) + err := suite.chainA.GetSimApp().TransferKeeper.ClaimCapability(suite.chainA.GetContext(), cap, host.PortPath(TestPortID)) + suite.Require().NoError(err) }, false, }, @@ -56,7 +59,6 @@ func (suite *KeeperTestSuite) TestRegisterInterchainAccount() { false, }, } - for _, tc := range testCases { tc := tc @@ -77,7 +79,6 @@ func (suite *KeeperTestSuite) TestRegisterInterchainAccount() { } else { suite.Require().Error(err) } - }) } } diff --git a/modules/apps/27-interchain-accounts/types/errors.go b/modules/apps/27-interchain-accounts/types/errors.go index 6ce9f2a9dea..7bb391dbe93 100644 --- a/modules/apps/27-interchain-accounts/types/errors.go +++ b/modules/apps/27-interchain-accounts/types/errors.go @@ -13,8 +13,8 @@ var ( ErrInvalidRoute = sdkerrors.Register(ModuleName, 7, "invalid route") ErrInterchainAccountNotFound = sdkerrors.Register(ModuleName, 8, "interchain account not found") ErrInterchainAccountAlreadySet = sdkerrors.Register(ModuleName, 9, "interchain account is already set") - ErrActiveChannelNotFound = sdkerrors.Register(ModuleName, 10, "no active channel for this owner") - ErrActiveChannelAlreadySet = sdkerrors.Register(ModuleName, 11, "active channel already set for this owner") + ErrActiveChannelAlreadySet = sdkerrors.Register(ModuleName, 10, "active channel already set for this owner") + ErrActiveChannelNotFound = sdkerrors.Register(ModuleName, 11, "no active channel for this owner") ErrInvalidVersion = sdkerrors.Register(ModuleName, 12, "invalid interchain accounts version") ErrInvalidAccountAddress = sdkerrors.Register(ModuleName, 13, "invalid account address") ErrUnsupported = sdkerrors.Register(ModuleName, 14, "interchain account does not support this action") From 578847c0f32a7249f283bd0d245d9eb7804b68b3 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 2 Feb 2022 16:30:49 +0100 Subject: [PATCH 014/140] refactor: reformat KeyOwnerAccount (#833) (#836) ## Description Reformats KeyOwnerAccount store key closes: [Damians Comment](https://github.com/cosmos/ibc-go/pull/823#pullrequestreview-870409614) --- 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. - [ ] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [ ] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit bbcc09c3dfa828f123191ade39d373b432483d6b) Co-authored-by: Sean King --- .../27-interchain-accounts/controller/keeper/keeper.go | 8 ++++---- modules/apps/27-interchain-accounts/host/keeper/keeper.go | 8 ++++---- modules/apps/27-interchain-accounts/types/keys.go | 4 ++-- modules/apps/27-interchain-accounts/types/keys_test.go | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/modules/apps/27-interchain-accounts/controller/keeper/keeper.go b/modules/apps/27-interchain-accounts/controller/keeper/keeper.go index 7216ccb7fa8..87af9ae9c6f 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/keeper.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/keeper.go @@ -167,7 +167,7 @@ func (k Keeper) IsActiveChannel(ctx sdk.Context, connectionID, portID string) bo // 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 := ctx.KVStore(k.storeKey) - key := icatypes.KeyOwnerAccount(connectionID, portID) + key := icatypes.KeyOwnerAccount(portID, connectionID) if !store.Has(key) { return "", false @@ -186,8 +186,8 @@ func (k Keeper) GetAllInterchainAccounts(ctx sdk.Context) []icatypes.RegisteredI keySplit := strings.Split(string(iterator.Key()), "/") acc := icatypes.RegisteredInterchainAccount{ - ConnectionId: keySplit[1], - PortId: keySplit[2], + ConnectionId: keySplit[2], + PortId: keySplit[1], AccountAddress: string(iterator.Value()), } @@ -200,5 +200,5 @@ func (k Keeper) GetAllInterchainAccounts(ctx sdk.Context) []icatypes.RegisteredI // SetInterchainAccountAddress stores the InterchainAccount address, keyed by the associated connectionID and portID func (k Keeper) SetInterchainAccountAddress(ctx sdk.Context, connectionID, portID, address string) { store := ctx.KVStore(k.storeKey) - store.Set(icatypes.KeyOwnerAccount(connectionID, portID), []byte(address)) + store.Set(icatypes.KeyOwnerAccount(portID, connectionID), []byte(address)) } diff --git a/modules/apps/27-interchain-accounts/host/keeper/keeper.go b/modules/apps/27-interchain-accounts/host/keeper/keeper.go index 181153a0fb5..ea3f8205c87 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/keeper.go +++ b/modules/apps/27-interchain-accounts/host/keeper/keeper.go @@ -156,7 +156,7 @@ func (k Keeper) IsActiveChannel(ctx sdk.Context, connectionID, portID string) bo // 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 := ctx.KVStore(k.storeKey) - key := icatypes.KeyOwnerAccount(connectionID, portID) + key := icatypes.KeyOwnerAccount(portID, connectionID) if !store.Has(key) { return "", false @@ -175,8 +175,8 @@ func (k Keeper) GetAllInterchainAccounts(ctx sdk.Context) []icatypes.RegisteredI keySplit := strings.Split(string(iterator.Key()), "/") acc := icatypes.RegisteredInterchainAccount{ - ConnectionId: keySplit[1], - PortId: keySplit[2], + ConnectionId: keySplit[2], + PortId: keySplit[1], AccountAddress: string(iterator.Value()), } @@ -189,5 +189,5 @@ func (k Keeper) GetAllInterchainAccounts(ctx sdk.Context) []icatypes.RegisteredI // SetInterchainAccountAddress stores the InterchainAccount address, keyed by the associated connectionID and portID func (k Keeper) SetInterchainAccountAddress(ctx sdk.Context, connectionID, portID, address string) { store := ctx.KVStore(k.storeKey) - store.Set(icatypes.KeyOwnerAccount(connectionID, portID), []byte(address)) + store.Set(icatypes.KeyOwnerAccount(portID, connectionID), []byte(address)) } diff --git a/modules/apps/27-interchain-accounts/types/keys.go b/modules/apps/27-interchain-accounts/types/keys.go index c2bde682551..2bf05ffda3f 100644 --- a/modules/apps/27-interchain-accounts/types/keys.go +++ b/modules/apps/27-interchain-accounts/types/keys.go @@ -44,8 +44,8 @@ func KeyActiveChannel(portID, connectionID string) []byte { } // KeyOwnerAccount creates and returns a new key used for interchain account store operations -func KeyOwnerAccount(connectionID, portID string) []byte { - return []byte(fmt.Sprintf("%s/%s/%s", OwnerKeyPrefix, connectionID, portID)) +func KeyOwnerAccount(portID, connectionID string) []byte { + return []byte(fmt.Sprintf("%s/%s/%s", OwnerKeyPrefix, portID, connectionID)) } // KeyPort creates and returns a new key used for port store operations diff --git a/modules/apps/27-interchain-accounts/types/keys_test.go b/modules/apps/27-interchain-accounts/types/keys_test.go index 02da485bf32..94c7a3bed0d 100644 --- a/modules/apps/27-interchain-accounts/types/keys_test.go +++ b/modules/apps/27-interchain-accounts/types/keys_test.go @@ -10,6 +10,6 @@ func (suite *TypesTestSuite) TestKeyActiveChannel() { } func (suite *TypesTestSuite) TestKeyOwnerAccount() { - key := types.KeyOwnerAccount("connection-id", "port-id") - suite.Require().Equal("owner/connection-id/port-id", string(key)) + key := types.KeyOwnerAccount("port-id", "connection-id") + suite.Require().Equal("owner/port-id/connection-id", string(key)) } From 39033089398386e4ffabd545589a64665c71096a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 2 Feb 2022 17:09:38 +0100 Subject: [PATCH 015/140] chore: adding encoding and txType fields to metadata (#824) (#837) * adding encoding and txTypes fields to ica metadata * adapting metadata and validation to support encoding and txTypes fields. updating test cases * renaming field to tx_type * fixing failing tests from merge (cherry picked from commit 5ae8e3538e5d9c9fb1f46408482f5cb14ec806f7) Co-authored-by: Damian Nolan --- docs/ibc/proto-docs.md | 2 + .../controller/ibc_module_test.go | 4 + .../controller/keeper/account.go | 10 +- .../controller/keeper/handshake_test.go | 54 ++++++- .../controller/keeper/keeper_test.go | 2 + .../host/ibc_module_test.go | 2 + .../host/keeper/handshake_test.go | 26 ++- .../host/keeper/keeper_test.go | 2 + .../27-interchain-accounts/types/metadata.go | 60 ++++++- .../types/metadata.pb.go | 148 +++++++++++++++--- .../types/metadata_test.go | 80 +++++++++- .../interchain_accounts/v1/metadata.proto | 4 + 12 files changed, 366 insertions(+), 28 deletions(-) diff --git a/docs/ibc/proto-docs.md b/docs/ibc/proto-docs.md index 667b8b9a4ee..a497c928af1 100644 --- a/docs/ibc/proto-docs.md +++ b/docs/ibc/proto-docs.md @@ -423,6 +423,8 @@ See ICS004: https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel- | `controller_connection_id` | [string](#string) | | controller_connection_id is the connection identifier associated with the controller chain | | `host_connection_id` | [string](#string) | | host_connection_id is the connection identifier associated with the host chain | | `address` | [string](#string) | | 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 | +| `encoding` | [string](#string) | | encoding defines the supported codec format | +| `tx_type` | [string](#string) | | tx_type defines the type of transactions the interchain account can execute | diff --git a/modules/apps/27-interchain-accounts/controller/ibc_module_test.go b/modules/apps/27-interchain-accounts/controller/ibc_module_test.go index 9fd7486cf1a..f2b5d36ac63 100644 --- a/modules/apps/27-interchain-accounts/controller/ibc_module_test.go +++ b/modules/apps/27-interchain-accounts/controller/ibc_module_test.go @@ -35,6 +35,8 @@ var ( Version: icatypes.Version, ControllerConnectionId: ibctesting.FirstConnectionID, HostConnectionId: ibctesting.FirstConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, })) ) @@ -675,6 +677,8 @@ func (suite *InterchainAccountsTestSuite) TestSingleHostMultipleControllers() { Version: icatypes.Version, ControllerConnectionId: pathCToB.EndpointA.ConnectionID, HostConnectionId: pathCToB.EndpointB.ConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, })) err = SetupICAPath(pathCToB, TestOwnerAddress) diff --git a/modules/apps/27-interchain-accounts/controller/keeper/account.go b/modules/apps/27-interchain-accounts/controller/keeper/account.go index 143c3a6055e..dd87348876e 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/account.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/account.go @@ -43,7 +43,15 @@ func (k Keeper) RegisterInterchainAccount(ctx sdk.Context, connectionID, owner s } // NOTE: An empty string is provided for accAddress, to be fulfilled upon OnChanOpenTry handshake step - metadata := icatypes.NewMetadata(icatypes.Version, connectionID, connectionEnd.GetCounterparty().GetConnectionID(), "") + metadata := icatypes.NewMetadata( + icatypes.Version, + connectionID, + connectionEnd.GetCounterparty().GetConnectionID(), + "", + icatypes.EncodingProtobuf, + icatypes.TxTypeSDKMultiMsg, + ) + versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) if err != nil { return err diff --git a/modules/apps/27-interchain-accounts/controller/keeper/handshake_test.go b/modules/apps/27-interchain-accounts/controller/keeper/handshake_test.go index b08a6913aaf..0f1e1af41a0 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/handshake_test.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/handshake_test.go @@ -60,6 +60,32 @@ func (suite *KeeperTestSuite) TestOnChanOpenInit() { }, false, }, + { + "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) + }, + false, + }, + { + "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) + }, + false, + }, { "connection not found", func() { @@ -144,7 +170,7 @@ func (suite *KeeperTestSuite) TestOnChanOpenInit() { path.EndpointA.ChannelConfig.PortID = portID // default values - metadata = icatypes.NewMetadata(icatypes.Version, ibctesting.FirstConnectionID, ibctesting.FirstConnectionID, "") + metadata = icatypes.NewMetadata(icatypes.Version, ibctesting.FirstConnectionID, ibctesting.FirstConnectionID, "", icatypes.EncodingProtobuf, icatypes.TxTypeSDKMultiMsg) versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) suite.Require().NoError(err) @@ -211,6 +237,30 @@ func (suite *KeeperTestSuite) TestOnChanOpenAck() { }, false, }, + { + "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) + }, + false, + }, + { + "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) + }, + false, + }, { "invalid account address", func() { @@ -275,7 +325,7 @@ func (suite *KeeperTestSuite) TestOnChanOpenAck() { err = path.EndpointB.ChanOpenTry() suite.Require().NoError(err) - metadata = icatypes.NewMetadata(icatypes.Version, ibctesting.FirstConnectionID, ibctesting.FirstConnectionID, TestAccAddress.String()) + metadata = icatypes.NewMetadata(icatypes.Version, ibctesting.FirstConnectionID, ibctesting.FirstConnectionID, TestAccAddress.String(), icatypes.EncodingProtobuf, icatypes.TxTypeSDKMultiMsg) versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) suite.Require().NoError(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 index eff4ce5fa96..c3e1a48ee0c 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/keeper_test.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/keeper_test.go @@ -30,6 +30,8 @@ var ( Version: icatypes.Version, ControllerConnectionId: ibctesting.FirstConnectionID, HostConnectionId: ibctesting.FirstConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, })) ) diff --git a/modules/apps/27-interchain-accounts/host/ibc_module_test.go b/modules/apps/27-interchain-accounts/host/ibc_module_test.go index ddf90c6d46c..0b312d34dd6 100644 --- a/modules/apps/27-interchain-accounts/host/ibc_module_test.go +++ b/modules/apps/27-interchain-accounts/host/ibc_module_test.go @@ -37,6 +37,8 @@ var ( Version: icatypes.Version, ControllerConnectionId: ibctesting.FirstConnectionID, HostConnectionId: ibctesting.FirstConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, })) ) diff --git a/modules/apps/27-interchain-accounts/host/keeper/handshake_test.go b/modules/apps/27-interchain-accounts/host/keeper/handshake_test.go index 6f3dc0d25e1..e920a55f0df 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/handshake_test.go +++ b/modules/apps/27-interchain-accounts/host/keeper/handshake_test.go @@ -66,6 +66,30 @@ func (suite *KeeperTestSuite) TestOnChanOpenTry() { }, false, }, + { + "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) + }, + false, + }, + { + "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) + }, + false, + }, { "invalid controller connection ID", func() { @@ -141,7 +165,7 @@ func (suite *KeeperTestSuite) TestOnChanOpenTry() { path.EndpointB.ChannelID = channeltypes.FormatChannelIdentifier(channelSequence) // default values - metadata = icatypes.NewMetadata(icatypes.Version, ibctesting.FirstConnectionID, ibctesting.FirstConnectionID, "") + metadata = icatypes.NewMetadata(icatypes.Version, ibctesting.FirstConnectionID, ibctesting.FirstConnectionID, "", icatypes.EncodingProtobuf, icatypes.TxTypeSDKMultiMsg) versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) suite.Require().NoError(err) diff --git a/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go b/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go index 2b38f6b5e84..96c80c45f99 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go +++ b/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go @@ -30,6 +30,8 @@ var ( Version: icatypes.Version, ControllerConnectionId: ibctesting.FirstConnectionID, HostConnectionId: ibctesting.FirstConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, })) ) diff --git a/modules/apps/27-interchain-accounts/types/metadata.go b/modules/apps/27-interchain-accounts/types/metadata.go index acc1dcb940b..67588e2d809 100644 --- a/modules/apps/27-interchain-accounts/types/metadata.go +++ b/modules/apps/27-interchain-accounts/types/metadata.go @@ -7,18 +7,36 @@ import ( connectiontypes "github.com/cosmos/ibc-go/v3/modules/core/03-connection/types" ) +const ( + // EncodingProtobuf defines the protocol buffers proto3 encoding format + EncodingProtobuf = "proto3" + + // 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 string) Metadata { +func NewMetadata(version, controllerConnectionID, hostConnectionID, accAddress, encoding, txType string) Metadata { return Metadata{ Version: version, ControllerConnectionId: controllerConnectionID, HostConnectionId: hostConnectionID, Address: accAddress, + Encoding: encoding, + TxType: txType, } } // ValidateControllerMetadata performs validation of the provided ICS27 controller metadata parameters func ValidateControllerMetadata(ctx sdk.Context, channelKeeper ChannelKeeper, connectionHops []string, metadata Metadata) error { + if !isSupportedEncoding(metadata.Encoding) { + return sdkerrors.Wrapf(ErrInvalidCodec, "unsupported encoding format %s", metadata.Encoding) + } + + if !isSupportedTxType(metadata.TxType) { + return sdkerrors.Wrapf(ErrUnknownDataType, "unsupported transaction type %s", metadata.TxType) + } + connection, err := channelKeeper.GetConnection(ctx, connectionHops[0]) if err != nil { return err @@ -43,6 +61,14 @@ func ValidateControllerMetadata(ctx sdk.Context, channelKeeper ChannelKeeper, co // 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 sdkerrors.Wrapf(ErrInvalidCodec, "unsupported encoding format %s", metadata.Encoding) + } + + if !isSupportedTxType(metadata.TxType) { + return sdkerrors.Wrapf(ErrUnknownDataType, "unsupported transaction type %s", metadata.TxType) + } + connection, err := channelKeeper.GetConnection(ctx, connectionHops[0]) if err != nil { return err @@ -65,6 +91,38 @@ func ValidateHostMetadata(ctx sdk.Context, channelKeeper ChannelKeeper, connecti return nil } +// isSupportedEncoding returns true if the provided encoding is supported, otherwise false +func isSupportedEncoding(encoding string) bool { + for _, enc := range getSupportedEncoding() { + if enc == encoding { + return true + } + } + + return false +} + +// getSupportedEncoding returns a string slice of supported encoding formats +func getSupportedEncoding() []string { + return []string{EncodingProtobuf} +} + +// isSupportedTxType returns true if the provided transaction type is supported, otherwise false +func isSupportedTxType(txType string) bool { + for _, t := range getSupportedTxTypes() { + if t == txType { + return true + } + } + + return false +} + +// 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 { diff --git a/modules/apps/27-interchain-accounts/types/metadata.pb.go b/modules/apps/27-interchain-accounts/types/metadata.pb.go index d3cc1214361..907e8c0c01d 100644 --- a/modules/apps/27-interchain-accounts/types/metadata.pb.go +++ b/modules/apps/27-interchain-accounts/types/metadata.pb.go @@ -35,6 +35,10 @@ type Metadata struct { // 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{} } @@ -98,6 +102,20 @@ func (m *Metadata) GetAddress() string { 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") } @@ -107,27 +125,29 @@ func init() { } var fileDescriptor_c29c32e397d1f21e = []byte{ - // 316 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x91, 0xcf, 0x4b, 0xc3, 0x30, - 0x14, 0xc7, 0x57, 0x15, 0xa7, 0x3d, 0x49, 0x11, 0x89, 0x82, 0x99, 0xd4, 0x83, 0x5e, 0xd6, 0x30, - 0x07, 0x0a, 0x1e, 0x27, 0x1e, 0x44, 0xbc, 0xec, 0x28, 0x48, 0x49, 0x5f, 0x42, 0x17, 0x68, 0xf3, - 0x4a, 0x92, 0x15, 0xf6, 0x5f, 0xf8, 0x67, 0x79, 0xdc, 0xd1, 0xd3, 0x90, 0xed, 0xe6, 0x71, 0x7f, - 0x81, 0x74, 0x9d, 0x9b, 0x3f, 0x6f, 0x79, 0x79, 0xdf, 0xcf, 0x27, 0xe4, 0x3d, 0xff, 0x52, 0x25, - 0xc0, 0x78, 0x51, 0x64, 0x0a, 0xb8, 0x53, 0xa8, 0x2d, 0x53, 0xda, 0x49, 0x03, 0x03, 0xae, 0x74, - 0xcc, 0x01, 0x70, 0xa8, 0x9d, 0x65, 0x65, 0x87, 0xe5, 0xd2, 0x71, 0xc1, 0x1d, 0x8f, 0x0a, 0x83, - 0x0e, 0x83, 0x33, 0x95, 0x40, 0xf4, 0x95, 0x8b, 0xfe, 0xe0, 0xa2, 0xb2, 0x73, 0xb4, 0x9f, 0x62, - 0x8a, 0x0b, 0x86, 0x55, 0xa7, 0x1a, 0x0f, 0xdf, 0x3d, 0x7f, 0xe7, 0x61, 0x69, 0x0c, 0x88, 0xdf, - 0x2c, 0xa5, 0xb1, 0x0a, 0x35, 0xf1, 0x4e, 0xbc, 0xf3, 0xdd, 0xfe, 0x67, 0x19, 0x3c, 0xf9, 0x04, - 0x50, 0x3b, 0x83, 0x59, 0x26, 0x4d, 0x0c, 0xa8, 0xb5, 0x84, 0xea, 0xb5, 0x58, 0x09, 0xb2, 0x51, - 0x45, 0x7b, 0xa7, 0xf3, 0x49, 0xab, 0x35, 0xe2, 0x79, 0x76, 0x1d, 0xfe, 0x97, 0x0c, 0xfb, 0x07, - 0xeb, 0xd6, 0xcd, 0xaa, 0x73, 0x27, 0x82, 0x7b, 0x3f, 0x18, 0xa0, 0x75, 0x3f, 0xc4, 0x9b, 0x0b, - 0xf1, 0xf1, 0x7c, 0xd2, 0x3a, 0xac, 0xc5, 0xbf, 0x33, 0x61, 0x7f, 0xaf, 0xba, 0xfc, 0x26, 0x23, - 0x7e, 0x93, 0x0b, 0x61, 0xa4, 0xb5, 0x64, 0xab, 0xfe, 0xc5, 0xb2, 0xec, 0xc5, 0x2f, 0x53, 0xea, - 0x8d, 0xa7, 0xd4, 0x7b, 0x9b, 0x52, 0xef, 0x79, 0x46, 0x1b, 0xe3, 0x19, 0x6d, 0xbc, 0xce, 0x68, - 0xe3, 0xf1, 0x36, 0x55, 0x6e, 0x30, 0x4c, 0x22, 0xc0, 0x9c, 0x01, 0xda, 0x1c, 0x2d, 0x53, 0x09, - 0xb4, 0x53, 0x64, 0x65, 0x97, 0xe5, 0x28, 0x86, 0x99, 0xb4, 0xd5, 0x76, 0x2c, 0xbb, 0xb8, 0x6a, - 0xaf, 0x07, 0xdc, 0x5e, 0x2d, 0xc6, 0x8d, 0x0a, 0x69, 0x93, 0xed, 0xc5, 0x50, 0xbb, 0x1f, 0x01, - 0x00, 0x00, 0xff, 0xff, 0xb0, 0xff, 0x53, 0x4e, 0xcd, 0x01, 0x00, 0x00, + // 349 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x91, 0xcf, 0x4a, 0xeb, 0x40, + 0x18, 0xc5, 0x9b, 0xde, 0x6b, 0x5b, 0x67, 0x25, 0x83, 0xe8, 0x58, 0x30, 0x95, 0xb8, 0xd0, 0x4d, + 0x33, 0xd4, 0x82, 0x82, 0xcb, 0x8a, 0x0b, 0x11, 0x37, 0xc5, 0x95, 0x20, 0x61, 0x32, 0x33, 0xa4, + 0x03, 0xc9, 0x7c, 0x21, 0x33, 0x0d, 0xed, 0x5b, 0xf8, 0x06, 0xbe, 0x8e, 0xcb, 0x2e, 0x5d, 0x15, + 0x69, 0xdf, 0xa0, 0x4f, 0x20, 0x49, 0x6b, 0xeb, 0xdf, 0x5d, 0x4e, 0xce, 0x39, 0xbf, 0x8f, 0xe1, + 0xa0, 0x73, 0x15, 0x72, 0xca, 0xd2, 0x34, 0x56, 0x9c, 0x59, 0x05, 0xda, 0x50, 0xa5, 0xad, 0xcc, + 0xf8, 0x80, 0x29, 0x1d, 0x30, 0xce, 0x61, 0xa8, 0xad, 0xa1, 0x79, 0x87, 0x26, 0xd2, 0x32, 0xc1, + 0x2c, 0xf3, 0xd3, 0x0c, 0x2c, 0xe0, 0x13, 0x15, 0x72, 0xff, 0x73, 0xcf, 0xff, 0xa5, 0xe7, 0xe7, + 0x9d, 0xe6, 0x6e, 0x04, 0x11, 0x94, 0x1d, 0x5a, 0x7c, 0x2d, 0xeb, 0xde, 0x73, 0x15, 0x35, 0xee, + 0x56, 0x44, 0x4c, 0x50, 0x3d, 0x97, 0x99, 0x51, 0xa0, 0x89, 0x73, 0xe4, 0x9c, 0x6e, 0xf7, 0x3f, + 0x24, 0x7e, 0x44, 0x84, 0x83, 0xb6, 0x19, 0xc4, 0xb1, 0xcc, 0x02, 0x0e, 0x5a, 0x4b, 0x5e, 0x5c, + 0x0b, 0x94, 0x20, 0xd5, 0x22, 0xda, 0x3b, 0x5e, 0x4c, 0x5b, 0xad, 0x31, 0x4b, 0xe2, 0x4b, 0xef, + 0xaf, 0xa4, 0xd7, 0xdf, 0xdb, 0x58, 0x57, 0x6b, 0xe7, 0x46, 0xe0, 0x5b, 0x84, 0x07, 0x60, 0xec, + 0x37, 0xf0, 0xbf, 0x12, 0x7c, 0xb8, 0x98, 0xb6, 0x0e, 0x96, 0xe0, 0x9f, 0x19, 0xaf, 0xbf, 0x53, + 0xfc, 0xfc, 0x02, 0x23, 0xa8, 0xce, 0x84, 0xc8, 0xa4, 0x31, 0xe4, 0xff, 0xf2, 0x15, 0x2b, 0x89, + 0x9b, 0xa8, 0x21, 0x35, 0x07, 0xa1, 0x74, 0x44, 0xb6, 0x4a, 0x6b, 0xad, 0xf1, 0x3e, 0xaa, 0xdb, + 0x51, 0x60, 0xc7, 0xa9, 0x24, 0xb5, 0xd2, 0xaa, 0xd9, 0xd1, 0xfd, 0x38, 0x95, 0xbd, 0xe0, 0x65, + 0xe6, 0x3a, 0x93, 0x99, 0xeb, 0xbc, 0xcd, 0x5c, 0xe7, 0x69, 0xee, 0x56, 0x26, 0x73, 0xb7, 0xf2, + 0x3a, 0x77, 0x2b, 0x0f, 0xd7, 0x91, 0xb2, 0x83, 0x61, 0xe8, 0x73, 0x48, 0x28, 0x07, 0x93, 0x80, + 0xa1, 0x2a, 0xe4, 0xed, 0x08, 0x68, 0xde, 0xa5, 0x09, 0x88, 0x61, 0x2c, 0x4d, 0x31, 0xa9, 0xa1, + 0x67, 0x17, 0xed, 0xcd, 0x2a, 0xed, 0xf5, 0x9a, 0xc5, 0x35, 0x13, 0xd6, 0xca, 0x25, 0xba, 0xef, + 0x01, 0x00, 0x00, 0xff, 0xff, 0x6e, 0x74, 0x28, 0x89, 0x02, 0x02, 0x00, 0x00, } func (m *Metadata) Marshal() (dAtA []byte, err error) { @@ -150,6 +170,20 @@ func (m *Metadata) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = 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) @@ -214,6 +248,14 @@ func (m *Metadata) Size() (n int) { 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 } @@ -380,6 +422,70 @@ func (m *Metadata) Unmarshal(dAtA []byte) error { } 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:]) diff --git a/modules/apps/27-interchain-accounts/types/metadata_test.go b/modules/apps/27-interchain-accounts/types/metadata_test.go index 2e569549647..1c53b8f7126 100644 --- a/modules/apps/27-interchain-accounts/types/metadata_test.go +++ b/modules/apps/27-interchain-accounts/types/metadata_test.go @@ -27,10 +27,40 @@ func (suite *TypesTestSuite) TestValidateControllerMetadata() { ControllerConnectionId: ibctesting.FirstConnectionID, HostConnectionId: ibctesting.FirstConnectionID, Address: "", + Encoding: types.EncodingProtobuf, + TxType: types.TxTypeSDKMultiMsg, } }, true, }, + { + "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, + } + }, + false, + }, + { + "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", + } + }, + false, + }, { "invalid controller connection", func() { @@ -39,6 +69,8 @@ func (suite *TypesTestSuite) TestValidateControllerMetadata() { ControllerConnectionId: "connection-10", HostConnectionId: ibctesting.FirstConnectionID, Address: TestOwnerAddress, + Encoding: types.EncodingProtobuf, + TxType: types.TxTypeSDKMultiMsg, } }, false, @@ -51,6 +83,8 @@ func (suite *TypesTestSuite) TestValidateControllerMetadata() { ControllerConnectionId: ibctesting.FirstConnectionID, HostConnectionId: "connection-10", Address: TestOwnerAddress, + Encoding: types.EncodingProtobuf, + TxType: types.TxTypeSDKMultiMsg, } }, false, @@ -63,6 +97,8 @@ func (suite *TypesTestSuite) TestValidateControllerMetadata() { ControllerConnectionId: ibctesting.FirstConnectionID, HostConnectionId: ibctesting.FirstConnectionID, Address: " ", + Encoding: types.EncodingProtobuf, + TxType: types.TxTypeSDKMultiMsg, } }, false, @@ -75,6 +111,8 @@ func (suite *TypesTestSuite) TestValidateControllerMetadata() { ControllerConnectionId: ibctesting.FirstConnectionID, HostConnectionId: ibctesting.FirstConnectionID, Address: TestOwnerAddress, + Encoding: types.EncodingProtobuf, + TxType: types.TxTypeSDKMultiMsg, } }, false, @@ -89,7 +127,7 @@ func (suite *TypesTestSuite) TestValidateControllerMetadata() { path := ibctesting.NewPath(suite.chainA, suite.chainB) suite.coordinator.SetupConnections(path) - metadata = types.NewMetadata(types.Version, ibctesting.FirstConnectionID, ibctesting.FirstConnectionID, TestOwnerAddress) + metadata = types.NewMetadata(types.Version, ibctesting.FirstConnectionID, ibctesting.FirstConnectionID, TestOwnerAddress, types.EncodingProtobuf, types.TxTypeSDKMultiMsg) tc.malleate() // malleate mutates test data @@ -131,10 +169,40 @@ func (suite *TypesTestSuite) TestValidateHostMetadata() { ControllerConnectionId: ibctesting.FirstConnectionID, HostConnectionId: ibctesting.FirstConnectionID, Address: "", + Encoding: types.EncodingProtobuf, + TxType: types.TxTypeSDKMultiMsg, } }, true, }, + { + "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, + } + }, + false, + }, + { + "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", + } + }, + false, + }, { "invalid controller connection", func() { @@ -143,6 +211,8 @@ func (suite *TypesTestSuite) TestValidateHostMetadata() { ControllerConnectionId: "connection-10", HostConnectionId: ibctesting.FirstConnectionID, Address: TestOwnerAddress, + Encoding: types.EncodingProtobuf, + TxType: types.TxTypeSDKMultiMsg, } }, false, @@ -155,6 +225,8 @@ func (suite *TypesTestSuite) TestValidateHostMetadata() { ControllerConnectionId: ibctesting.FirstConnectionID, HostConnectionId: "connection-10", Address: TestOwnerAddress, + Encoding: types.EncodingProtobuf, + TxType: types.TxTypeSDKMultiMsg, } }, false, @@ -167,6 +239,8 @@ func (suite *TypesTestSuite) TestValidateHostMetadata() { ControllerConnectionId: ibctesting.FirstConnectionID, HostConnectionId: ibctesting.FirstConnectionID, Address: " ", + Encoding: types.EncodingProtobuf, + TxType: types.TxTypeSDKMultiMsg, } }, false, @@ -179,6 +253,8 @@ func (suite *TypesTestSuite) TestValidateHostMetadata() { ControllerConnectionId: ibctesting.FirstConnectionID, HostConnectionId: ibctesting.FirstConnectionID, Address: TestOwnerAddress, + Encoding: types.EncodingProtobuf, + TxType: types.TxTypeSDKMultiMsg, } }, false, @@ -193,7 +269,7 @@ func (suite *TypesTestSuite) TestValidateHostMetadata() { path := ibctesting.NewPath(suite.chainA, suite.chainB) suite.coordinator.SetupConnections(path) - metadata = types.NewMetadata(types.Version, ibctesting.FirstConnectionID, ibctesting.FirstConnectionID, TestOwnerAddress) + metadata = types.NewMetadata(types.Version, ibctesting.FirstConnectionID, ibctesting.FirstConnectionID, TestOwnerAddress, types.EncodingProtobuf, types.TxTypeSDKMultiMsg) tc.malleate() // malleate mutates test data diff --git a/proto/ibc/applications/interchain_accounts/v1/metadata.proto b/proto/ibc/applications/interchain_accounts/v1/metadata.proto index acc338466af..3eab1d04a69 100644 --- a/proto/ibc/applications/interchain_accounts/v1/metadata.proto +++ b/proto/ibc/applications/interchain_accounts/v1/metadata.proto @@ -18,4 +18,8 @@ message Metadata { // 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; } From 8306273a5b1dbf828e4af5249896b1cfa2da5906 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 2 Feb 2022 17:10:49 +0100 Subject: [PATCH 016/140] refactor: include transaction response in ics27 channel acknowledgement (#811) (#838) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description ref: #701 --- 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. - [x] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [x] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [x] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [x] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [x] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [x] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [x] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [x] Re-reviewed `Files changed` in the Github PR explorer - [x] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit 6c48f7e57a53504790b4759f05670fd46ce37b00) Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> --- .../interchain-accounts/auth-modules.md | 117 ++++++++++++++++++ .../27-interchain-accounts/host/ibc_module.go | 11 +- .../host/ibc_module_test.go | 87 ++++++++++++- .../host/keeper/relay.go | 66 ++++++---- .../host/keeper/relay_test.go | 4 +- 5 files changed, 256 insertions(+), 29 deletions(-) diff --git a/docs/app-modules/interchain-accounts/auth-modules.md b/docs/app-modules/interchain-accounts/auth-modules.md index 4d8b126dd2f..b87265d4e28 100644 --- a/docs/app-modules/interchain-accounts/auth-modules.md +++ b/docs/app-modules/interchain-accounts/auth-modules.md @@ -210,6 +210,123 @@ seq, err = keeper.icaControllerKeeper.SendTx(ctx, chanCap, portID, packetData, t 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 +txMsgData := &sdk.TxMsgData{} +if err := proto.Unmarshal(ack.Acknowledgement(), 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: + 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.TypeURL { +case banktypes.MsgSend: + msgResponse := &banktypes.MsgSendResponse{} + if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { + return err + } + + handleBankSendMsg(msgResponse) + +case stakingtypes.MsgDelegate: + msgResponse := &stakingtypes.MsgDelegateResponse{} + if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { + return err + } + + handleStakingDelegateMsg(msgResponse) + +case 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 continued +default: + 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](./integration.md#example-integration). diff --git a/modules/apps/27-interchain-accounts/host/ibc_module.go b/modules/apps/27-interchain-accounts/host/ibc_module.go index cec21d258c3..be302e09151 100644 --- a/modules/apps/27-interchain-accounts/host/ibc_module.go +++ b/modules/apps/27-interchain-accounts/host/ibc_module.go @@ -108,17 +108,16 @@ func (im IBCModule) OnRecvPacket( return channeltypes.NewErrorAcknowledgement(types.ErrHostSubModuleDisabled.Error()) } - ack := channeltypes.NewResultAcknowledgement([]byte{byte(1)}) - - if err := im.keeper.OnRecvPacket(ctx, packet); err != nil { - ack = types.NewErrorAcknowledgement(err) - + txResponse, err := im.keeper.OnRecvPacket(ctx, packet) + if err != nil { // Emit an event including the error msg keeper.EmitWriteErrorAcknowledgementEvent(ctx, packet, err) + + return types.NewErrorAcknowledgement(err) } // NOTE: acknowledgement will be written synchronously during IBC handler execution. - return ack + return channeltypes.NewResultAcknowledgement(txResponse) } // OnAcknowledgementPacket implements the IBCModule interface diff --git a/modules/apps/27-interchain-accounts/host/ibc_module_test.go b/modules/apps/27-interchain-accounts/host/ibc_module_test.go index 0b312d34dd6..56baa70e847 100644 --- a/modules/apps/27-interchain-accounts/host/ibc_module_test.go +++ b/modules/apps/27-interchain-accounts/host/ibc_module_test.go @@ -7,8 +7,12 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + "github.com/gogo/protobuf/proto" "github.com/stretchr/testify/suite" + abcitypes "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" + tmprotostate "github.com/tendermint/tendermint/proto/tendermint/state" + tmstate "github.com/tendermint/tendermint/state" "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/host/types" icatypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/types" @@ -460,6 +464,22 @@ func (suite *InterchainAccountsTestSuite) TestOnRecvPacket() { } packetData = icaPacketData.GetBytes() + // build expected ack + msgResponseBz, err := proto.Marshal(&banktypes.MsgSendResponse{}) + suite.Require().NoError(err) + + msgData := &sdk.MsgData{ + MsgType: sdk.MsgTypeURL(msg), + Data: msgResponseBz, + } + + expectedTxResponse, err := proto.Marshal(&sdk.TxMsgData{ + Data: []*sdk.MsgData{msgData}, + }) + 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) @@ -478,7 +498,12 @@ func (suite *InterchainAccountsTestSuite) TestOnRecvPacket() { suite.Require().True(ok) ack := cbs.OnRecvPacket(suite.chainB.GetContext(), packet, nil) - suite.Require().Equal(tc.expAckSuccess, ack.Success()) + if tc.expAckSuccess { + suite.Require().True(ack.Success()) + suite.Require().Equal(expectedAck, ack) + } else { + suite.Require().False(ack.Success()) + } }) } @@ -687,3 +712,63 @@ func (suite *InterchainAccountsTestSuite) TestControlAccountAfterChannelClose() hasBalance = suite.chainB.GetSimApp().BankKeeper.HasBalance(suite.chainB.GetContext(), icaAddr, sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: sdk.NewInt(0)}) suite.Require().True(hasBalance) } + +// The safety of including SDK MsgResponses in the acknowledgement rests +// on the inclusion of the abcitypes.ResponseDeliverTx.Data in the +// abcitypes.ResposneDeliverTx hash. If the abcitypes.ResponseDeliverTx.Data +// gets removed from consensus they must no longer be used in the packet +// acknowledgement. +// +// This test acts as an indicator that the abcitypes.ResponseDeliverTx.Data +// may no longer be deterministic. +func (suite *InterchainAccountsTestSuite) TestABCICodeDeterminism() { + msgResponseBz, err := proto.Marshal(&channeltypes.MsgChannelOpenInitResponse{}) + suite.Require().NoError(err) + + msgData := &sdk.MsgData{ + MsgType: sdk.MsgTypeURL(&channeltypes.MsgChannelOpenInit{}), + Data: msgResponseBz, + } + + txResponse, err := proto.Marshal(&sdk.TxMsgData{ + Data: []*sdk.MsgData{msgData}, + }) + suite.Require().NoError(err) + + deliverTx := abcitypes.ResponseDeliverTx{ + Data: txResponse, + } + responses := tmprotostate.ABCIResponses{ + DeliverTxs: []*abcitypes.ResponseDeliverTx{ + &deliverTx, + }, + } + + differentMsgResponseBz, err := proto.Marshal(&channeltypes.MsgRecvPacketResponse{}) + suite.Require().NoError(err) + + differentMsgData := &sdk.MsgData{ + MsgType: sdk.MsgTypeURL(&channeltypes.MsgRecvPacket{}), + Data: differentMsgResponseBz, + } + + differentTxResponse, err := proto.Marshal(&sdk.TxMsgData{ + Data: []*sdk.MsgData{differentMsgData}, + }) + suite.Require().NoError(err) + + differentDeliverTx := abcitypes.ResponseDeliverTx{ + Data: differentTxResponse, + } + + differentResponses := tmprotostate.ABCIResponses{ + DeliverTxs: []*abcitypes.ResponseDeliverTx{ + &differentDeliverTx, + }, + } + + hash := tmstate.ABCIResponsesResultsHash(&responses) + differentHash := tmstate.ABCIResponsesResultsHash(&differentResponses) + + suite.Require().NotEqual(hash, differentHash) +} diff --git a/modules/apps/27-interchain-accounts/host/keeper/relay.go b/modules/apps/27-interchain-accounts/host/keeper/relay.go index 803045794ad..8610a50f5e5 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/relay.go +++ b/modules/apps/27-interchain-accounts/host/keeper/relay.go @@ -3,66 +3,89 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/gogo/protobuf/proto" "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/host/types" icatypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/types" channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" ) -// OnRecvPacket handles a given interchain accounts packet on a destination host chain -func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet) error { +// 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 if err := icatypes.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { // UnmarshalJSON errors are indeterminate and therefore are not wrapped and included in failed acks - return sdkerrors.Wrapf(icatypes.ErrUnknownDataType, "cannot unmarshal ICS-27 interchain account packet data") + return nil, sdkerrors.Wrapf(icatypes.ErrUnknownDataType, "cannot unmarshal ICS-27 interchain account packet data") } switch data.Type { case icatypes.EXECUTE_TX: msgs, err := icatypes.DeserializeCosmosTx(k.cdc, data.Data) if err != nil { - return err + return nil, err } - if err = k.executeTx(ctx, packet.SourcePort, packet.DestinationPort, packet.DestinationChannel, msgs); err != nil { - return err + txResponse, err := k.executeTx(ctx, packet.SourcePort, packet.DestinationPort, packet.DestinationChannel, msgs) + if err != nil { + return nil, err } - return nil + return txResponse, nil default: - return icatypes.ErrUnknownDataType + return nil, icatypes.ErrUnknownDataType } } -func (k Keeper) executeTx(ctx sdk.Context, sourcePort, destPort, destChannel string, msgs []sdk.Msg) error { +// 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 channeltypes.ErrChannelNotFound + return nil, channeltypes.ErrChannelNotFound } if err := k.authenticateTx(ctx, msgs, channel.ConnectionHops[0], sourcePort); err != nil { - return err + return nil, err + } + + txMsgData := &sdk.TxMsgData{ + Data: make([]*sdk.MsgData, 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 _, msg := range msgs { + for i, msg := range msgs { if err := msg.ValidateBasic(); err != nil { - return err + return nil, err + } + + msgResponse, err := k.executeMsg(cacheCtx, msg) + if err != nil { + return nil, err } - if err := k.executeMsg(cacheCtx, msg); err != nil { - return err + txMsgData.Data[i] = &sdk.MsgData{ + MsgType: sdk.MsgTypeURL(msg), + Data: msgResponse, } + } // NOTE: The context returned by CacheContext() creates a new EventManager, so events must be correctly propagated back to the current context ctx.EventManager().EmitEvents(cacheCtx.EventManager().Events()) writeCache() - return nil + txResponse, err := proto.Marshal(txMsgData) + if err != nil { + return nil, sdkerrors.Wrap(err, "failed to marshal tx data") + } + + return txResponse, nil } // authenticateTx ensures the provided msgs contain the correct interchain account signer address retrieved @@ -89,20 +112,21 @@ func (k Keeper) authenticateTx(ctx sdk.Context, msgs []sdk.Msg, connectionID, po return nil } -// Attempts to get the message handler from the router and if found will then execute the message -func (k Keeper) executeMsg(ctx sdk.Context, msg sdk.Msg) error { +// 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) ([]byte, error) { handler := k.msgRouter.Handler(msg) if handler == nil { - return icatypes.ErrInvalidRoute + return nil, icatypes.ErrInvalidRoute } res, err := handler(ctx, msg) if err != nil { - return err + 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()) - return nil + return res.Data, 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 index f128804b415..fda31c34ef0 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/relay_test.go +++ b/modules/apps/27-interchain-accounts/host/keeper/relay_test.go @@ -424,12 +424,14 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() { 0, ) - err = suite.chainB.GetSimApp().ICAHostKeeper.OnRecvPacket(suite.chainB.GetContext(), packet) + txResponse, err := suite.chainB.GetSimApp().ICAHostKeeper.OnRecvPacket(suite.chainB.GetContext(), packet) if tc.expPass { suite.Require().NoError(err) + suite.Require().NotNil(txResponse) } else { suite.Require().Error(err) + suite.Require().Nil(txResponse) } }) } From a59c0339d75f31c5047f3e11d5d475988083e7a6 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 3 Feb 2022 13:18:40 +0100 Subject: [PATCH 017/140] test: ensure ics27 optimistic packet sends are disallowed (#842) (#843) (cherry picked from commit c7ea0e850e927d497bbe1fff6ff97d27fbc190d5) Co-authored-by: Damian Nolan --- .../27-interchain-accounts/controller/keeper/relay.go | 2 +- .../controller/keeper/relay_test.go | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/modules/apps/27-interchain-accounts/controller/keeper/relay.go b/modules/apps/27-interchain-accounts/controller/keeper/relay.go index 4375b3e3ee0..4eaf26c52a6 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/relay.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/relay.go @@ -16,7 +16,7 @@ import ( // 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. func (k Keeper) SendTx(ctx sdk.Context, chanCap *capabilitytypes.Capability, connectionID, portID string, icaPacketData icatypes.InterchainAccountPacketData, timeoutTimestamp uint64) (uint64, error) { - activeChannelID, found := k.GetActiveChannelID(ctx, connectionID, portID) + activeChannelID, found := k.GetOpenActiveChannel(ctx, connectionID, portID) if !found { return 0, sdkerrors.Wrapf(icatypes.ErrActiveChannelNotFound, "failed to retrieve active channel on connection %s for port %s", connectionID, portID) } diff --git a/modules/apps/27-interchain-accounts/controller/keeper/relay_test.go b/modules/apps/27-interchain-accounts/controller/keeper/relay_test.go index e687e0a751e..bb4d17b8232 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/relay_test.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/relay_test.go @@ -100,6 +100,17 @@ func (suite *KeeperTestSuite) TestSendTx() { }, false, }, + { + "channel in INIT state - optimistic packet sends fail", + func() { + channel, found := suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.GetChannel(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + suite.Require().True(found) + + channel.State = channeltypes.INIT + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.SetChannel(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, channel) + }, + false, + }, { "sendPacket fails - channel closed", func() { From 53cad5d6d72b640d66c271ed9ee68bb3650fb93a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 3 Feb 2022 14:53:10 +0100 Subject: [PATCH 018/140] ADR 003: ICS27 Ack format (#812) (#844) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description closes: #701 --- 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. - [ ] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [ ] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit 1021617e41007e3f68b7ad7ab4dcfc9a8cbb9658) Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> --- docs/architecture/README.md | 1 + .../adr-003-ics27-acknowledgement.md | 118 ++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 docs/architecture/adr-003-ics27-acknowledgement.md diff --git a/docs/architecture/README.md b/docs/architecture/README.md index d61da19b5fd..091ba899dc6 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -29,6 +29,7 @@ To suggest an ADR, please make use of the [ADR template](./adr-template.md) prov | ------ | ----------- | ------ | | [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 | | [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 mechansisms | Accepted | diff --git a/docs/architecture/adr-003-ics27-acknowledgement.md b/docs/architecture/adr-003-ics27-acknowledgement.md new file mode 100644 index 00000000000..dc2e3256ba6 --- /dev/null +++ b/docs/architecture/adr-003-ics27-acknowledgement.md @@ -0,0 +1,118 @@ +# 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 determinstic 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 determinstic 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 antcipation 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 fowards 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 verison. +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-beta1/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 determinstic. +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-beta1/modules/apps/27-interchain-accounts/host/types/ack_test.go#L41-#L82) to fail if the ABCI code is no longer determinstic. + +## 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-determinstic and cannot be included in the packet acknowledgement + +### Neutral + +No neutral consequences. + From bb867fdb0c8f57d0947f05343386c4583afe992e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 3 Feb 2022 15:46:27 +0100 Subject: [PATCH 019/140] docs: add security model to ics27 docs (#841) (#845) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Wording could maybe be improved, but I think the content is good enough closes: #705 --- 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. - [ ] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [ ] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit ec36c75e1a84abafedc797e8a60823ba7c4e762f) Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> --- docs/app-modules/interchain-accounts/overview.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/app-modules/interchain-accounts/overview.md b/docs/app-modules/interchain-accounts/overview.md index ada5c74336e..dc015aaf1e3 100644 --- a/docs/app-modules/interchain-accounts/overview.md +++ b/docs/app-modules/interchain-accounts/overview.md @@ -29,4 +29,10 @@ Regular accounts use a private key to sign transactions on-chain. Interchain Acc `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. From 32629696717dad00e600cf264021c88f4d92e209 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 4 Feb 2022 15:48:57 +0100 Subject: [PATCH 020/140] test: Register using same owner address on multiple connections (#846) (#849) (cherry picked from commit f6a9279936c3571268c39e4e09faa236ed3e82b9) Co-authored-by: Sean King --- .../controller/keeper/account_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/modules/apps/27-interchain-accounts/controller/keeper/account_test.go b/modules/apps/27-interchain-accounts/controller/keeper/account_test.go index 11334c332ea..9387baa3936 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/account_test.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/account_test.go @@ -82,3 +82,21 @@ func (suite *KeeperTestSuite) TestRegisterInterchainAccount() { }) } } + +func (suite *KeeperTestSuite) TestRegisterSameOwnerMultipleConnections() { + suite.SetupTest() + + owner := TestOwnerAddress + + path := NewICAPath(suite.chainA, suite.chainB) + suite.coordinator.SetupConnections(path) + + path2 := NewICAPath(suite.chainA, suite.chainC) + suite.coordinator.SetupConnections(path2) + + err := suite.chainA.GetSimApp().ICAControllerKeeper.RegisterInterchainAccount(suite.chainA.GetContext(), path.EndpointA.ConnectionID, owner) + suite.Require().NoError(err) + + err = suite.chainA.GetSimApp().ICAControllerKeeper.RegisterInterchainAccount(suite.chainA.GetContext(), path2.EndpointA.ConnectionID, owner) + suite.Require().NoError(err) +} From c53829f634c1316f524cfc222d4db2973c2820ca Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 7 Feb 2022 12:07:12 +0100 Subject: [PATCH 021/140] Move emissions to functions (#783) (#855) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * WIP move emissions * refactor events emission * update typo and code clean up following review * Update modules/core/03-connection/keeper/events.go Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> * Update modules/core/03-connection/keeper/events.go Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> * refactore based on code review Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> (cherry picked from commit 81b619d7436712437ef3ccb6b32111d437db58c6) Co-authored-by: nir1218 --- modules/core/02-client/keeper/client.go | 32 ++--- modules/core/02-client/keeper/events.go | 93 +++++++++++++ modules/core/02-client/keeper/proposal.go | 9 +- modules/core/03-connection/keeper/events.go | 75 ++++++++++ .../core/03-connection/keeper/handshake.go | 9 ++ modules/core/04-channel/keeper/events.go | 113 ++++++++++++++++ modules/core/04-channel/keeper/handshake.go | 92 +------------ modules/core/keeper/msg_server.go | 128 ++---------------- 8 files changed, 315 insertions(+), 236 deletions(-) create mode 100644 modules/core/02-client/keeper/events.go create mode 100644 modules/core/03-connection/keeper/events.go diff --git a/modules/core/02-client/keeper/client.go b/modules/core/02-client/keeper/client.go index 8e0a0639316..d8085ac719f 100644 --- a/modules/core/02-client/keeper/client.go +++ b/modules/core/02-client/keeper/client.go @@ -51,6 +51,8 @@ func (k Keeper) CreateClient( ) }() + EmitCreateClientEvent(ctx, clientID, clientState) + return clientID, nil } @@ -67,8 +69,6 @@ func (k Keeper) UpdateClient(ctx sdk.Context, clientID string, header exported.H return sdkerrors.Wrapf(types.ErrClientNotActive, "cannot update client (%s) with status %s", clientID, status) } - eventType := types.EventTypeUpdateClient - // Any writes made in CheckHeaderAndUpdateState are persisted on both valid updates and misbehaviour updates. // Light client implementations are responsible for writing the correct metadata (if any) in either case. newClientState, newConsensusState, err := clientState.CheckHeaderAndUpdateState(ctx, k.cdc, clientStore, header) @@ -118,9 +118,10 @@ func (k Keeper) UpdateClient(ctx sdk.Context, clientID string, header exported.H }, ) }() + + // emitting events in the keeper emits for both begin block and handler client updates + EmitUpdateClientEvent(ctx, clientID, newClientState, consensusHeight, headerStr) } else { - // set eventType to SubmitMisbehaviour - eventType = types.EventTypeSubmitMisbehaviour k.Logger(ctx).Info("client frozen due to misbehaviour", "client-id", clientID) @@ -135,18 +136,10 @@ func (k Keeper) UpdateClient(ctx sdk.Context, clientID string, header exported.H }, ) }() + + EmitSubmitMisbehaviourEventOnUpdate(ctx, clientID, newClientState, consensusHeight, headerStr) } - // emitting events in the keeper emits for both begin block and handler client updates - ctx.EventManager().EmitEvent( - sdk.NewEvent( - eventType, - sdk.NewAttribute(types.AttributeKeyClientID, clientID), - sdk.NewAttribute(types.AttributeKeyClientType, clientState.ClientType()), - sdk.NewAttribute(types.AttributeKeyConsensusHeight, consensusHeight.String()), - sdk.NewAttribute(types.AttributeKeyHeader, headerStr), - ), - ) return nil } @@ -188,14 +181,7 @@ func (k Keeper) UpgradeClient(ctx sdk.Context, clientID string, upgradedClient e }() // emitting events in the keeper emits for client upgrades - ctx.EventManager().EmitEvent( - sdk.NewEvent( - types.EventTypeUpgradeClient, - sdk.NewAttribute(types.AttributeKeyClientID, clientID), - sdk.NewAttribute(types.AttributeKeyClientType, updatedClientState.ClientType()), - sdk.NewAttribute(types.AttributeKeyConsensusHeight, updatedClientState.GetLatestHeight().String()), - ), - ) + EmitUpgradeClientEvent(ctx, clientID, updatedClientState) return nil } @@ -237,5 +223,7 @@ func (k Keeper) CheckMisbehaviourAndUpdateState(ctx sdk.Context, misbehaviour ex ) }() + EmitSubmitMisbehaviourEvent(ctx, misbehaviour.GetClientID(), clientState) + return nil } diff --git a/modules/core/02-client/keeper/events.go b/modules/core/02-client/keeper/events.go new file mode 100644 index 00000000000..ff8ae1c3acd --- /dev/null +++ b/modules/core/02-client/keeper/events.go @@ -0,0 +1,93 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" +) + +// EmitCreateClientEvent emits a create client event +func EmitCreateClientEvent(ctx sdk.Context, clientID string, clientState exported.ClientState) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeCreateClient, + sdk.NewAttribute(types.AttributeKeyClientID, clientID), + sdk.NewAttribute(types.AttributeKeyClientType, clientState.ClientType()), + sdk.NewAttribute(types.AttributeKeyConsensusHeight, clientState.GetLatestHeight().String()), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// EmitUpdateClientEvent emits an update client event +func EmitUpdateClientEvent(ctx sdk.Context, clientID string, clientState exported.ClientState, consensusHeight exported.Height, headerStr string) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeUpdateClient, + sdk.NewAttribute(types.AttributeKeyClientID, clientID), + sdk.NewAttribute(types.AttributeKeyClientType, clientState.ClientType()), + sdk.NewAttribute(types.AttributeKeyConsensusHeight, consensusHeight.String()), + sdk.NewAttribute(types.AttributeKeyHeader, headerStr), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// EmitUpdateClientEvent emits an upgrade client event +func EmitUpgradeClientEvent(ctx sdk.Context, clientID string, clientState exported.ClientState) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeUpgradeClient, + sdk.NewAttribute(types.AttributeKeyClientID, clientID), + sdk.NewAttribute(types.AttributeKeyClientType, clientState.ClientType()), + sdk.NewAttribute(types.AttributeKeyConsensusHeight, clientState.GetLatestHeight().String()), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// EmitUpdateClientProposalEvent emits an update client proposal event +func EmitUpdateClientProposalEvent(ctx sdk.Context, clientID string, clientState exported.ClientState) { + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeUpdateClientProposal, + sdk.NewAttribute(types.AttributeKeySubjectClientID, clientID), + sdk.NewAttribute(types.AttributeKeyClientType, clientState.ClientType()), + sdk.NewAttribute(types.AttributeKeyConsensusHeight, clientState.GetLatestHeight().String()), + ), + ) +} + +// EmitSubmitMisbehaviourEvent emits a client misbehaviour event +func EmitSubmitMisbehaviourEvent(ctx sdk.Context, clientID string, clientState exported.ClientState) { + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeSubmitMisbehaviour, + sdk.NewAttribute(types.AttributeKeyClientID, clientID), + sdk.NewAttribute(types.AttributeKeyClientType, clientState.ClientType()), + ), + ) +} + +// EmitSubmitMisbehaviourEventOnUpdate emits a client misbehaviour event on a client update event +func EmitSubmitMisbehaviourEventOnUpdate(ctx sdk.Context, clientID string, clientState exported.ClientState, consensusHeight exported.Height, headerStr string) { + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeSubmitMisbehaviour, + sdk.NewAttribute(types.AttributeKeyClientID, clientID), + sdk.NewAttribute(types.AttributeKeyClientType, clientState.ClientType()), + sdk.NewAttribute(types.AttributeKeyConsensusHeight, consensusHeight.String()), + sdk.NewAttribute(types.AttributeKeyHeader, headerStr), + ), + ) +} diff --git a/modules/core/02-client/keeper/proposal.go b/modules/core/02-client/keeper/proposal.go index bc210df85ce..ef0bf043e50 100644 --- a/modules/core/02-client/keeper/proposal.go +++ b/modules/core/02-client/keeper/proposal.go @@ -70,14 +70,7 @@ func (k Keeper) ClientUpdateProposal(ctx sdk.Context, p *types.ClientUpdatePropo }() // emitting events in the keeper for proposal updates to clients - ctx.EventManager().EmitEvent( - sdk.NewEvent( - types.EventTypeUpdateClientProposal, - sdk.NewAttribute(types.AttributeKeySubjectClientID, p.SubjectClientId), - sdk.NewAttribute(types.AttributeKeyClientType, clientState.ClientType()), - sdk.NewAttribute(types.AttributeKeyConsensusHeight, clientState.GetLatestHeight().String()), - ), - ) + EmitUpdateClientProposalEvent(ctx, p.SubjectClientId, clientState) return nil } diff --git a/modules/core/03-connection/keeper/events.go b/modules/core/03-connection/keeper/events.go new file mode 100644 index 00000000000..6b1636ea71e --- /dev/null +++ b/modules/core/03-connection/keeper/events.go @@ -0,0 +1,75 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v3/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.NewAttribute(types.AttributeKeyCounterpartyConnectionID, counterparty.ConnectionId), + ), + 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/handshake.go b/modules/core/03-connection/keeper/handshake.go index ed209c26cf2..177324e3e17 100644 --- a/modules/core/03-connection/keeper/handshake.go +++ b/modules/core/03-connection/keeper/handshake.go @@ -50,6 +50,8 @@ func (k Keeper) ConnOpenInit( telemetry.IncrCounter(1, "ibc", "connection", "open-init") }() + EmitConnectionOpenInitEvent(ctx, connectionID, clientID, counterparty) + return connectionID, nil } @@ -185,6 +187,8 @@ func (k Keeper) ConnOpenTry( telemetry.IncrCounter(1, "ibc", "connection", "open-try") }() + EmitConnectionOpenTryEvent(ctx, connectionID, clientID, counterparty) + return connectionID, nil } @@ -290,6 +294,9 @@ func (k Keeper) ConnOpenAck( connection.Versions = []*types.Version{version} connection.Counterparty.ConnectionId = counterpartyConnectionID k.SetConnection(ctx, connectionID, connection) + + EmitConnectionOpenAckEvent(ctx, connectionID, connection) + return nil } @@ -338,5 +345,7 @@ func (k Keeper) ConnOpenConfirm( telemetry.IncrCounter(1, "ibc", "connection", "open-confirm") }() + EmitConnectionOpenConfirmEvent(ctx, connectionID, connection) + return nil } diff --git a/modules/core/04-channel/keeper/events.go b/modules/core/04-channel/keeper/events.go index 1a5b44dbb1c..d299d6afeda 100644 --- a/modules/core/04-channel/keeper/events.go +++ b/modules/core/04-channel/keeper/events.go @@ -10,6 +10,119 @@ import ( "github.com/cosmos/ibc-go/v3/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.AttributeCounterpartyChannelID, channel.Counterparty.ChannelId), + sdk.NewAttribute(types.AttributeKeyConnectionID, channel.ConnectionHops[0]), + ), + }) + + ctx.EventManager().EmitEvents(sdk.Events{ + 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]), + ), + }) + ctx.EventManager().EmitEvents(sdk.Events{ + 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 exported.PacketI, channel types.Channel, timeoutHeight exported.Height) { diff --git a/modules/core/04-channel/keeper/handshake.go b/modules/core/04-channel/keeper/handshake.go index a9c5046c099..1d650bb4d7b 100644 --- a/modules/core/04-channel/keeper/handshake.go +++ b/modules/core/04-channel/keeper/handshake.go @@ -89,23 +89,7 @@ func (k Keeper) WriteOpenInitChannel( telemetry.IncrCounter(1, "ibc", "channel", "open-init") }() - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - types.EventTypeChannelOpenInit, - sdk.NewAttribute(types.AttributeKeyPortID, portID), - sdk.NewAttribute(types.AttributeKeyChannelID, channelID), - sdk.NewAttribute(types.AttributeCounterpartyPortID, counterparty.PortId), - sdk.NewAttribute(types.AttributeCounterpartyChannelID, counterparty.ChannelId), - sdk.NewAttribute(types.AttributeKeyConnectionID, connectionHops[0]), - ), - }) - - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), - ), - }) + EmitChannelOpenInitEvent(ctx, portID, channelID, channel) } // ChanOpenTry is called by a module to accept the first step of a channel opening @@ -263,22 +247,7 @@ func (k Keeper) WriteOpenTryChannel( telemetry.IncrCounter(1, "ibc", "channel", "open-try") }() - 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]), - ), - }) - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), - ), - }) + EmitChannelOpenTryEvent(ctx, portID, channelID, channel) } // ChanOpenAck is called by the handshake-originating module to acknowledge the @@ -366,23 +335,7 @@ func (k Keeper) WriteOpenAckChannel( telemetry.IncrCounter(1, "ibc", "channel", "open-ack") }() - 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]), - ), - }) - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), - ), - }) - + EmitChannelOpenAckEvent(ctx, portID, channelID, channel) } // ChanOpenConfirm is called by the counterparty module to close their end of the @@ -462,22 +415,7 @@ func (k Keeper) WriteOpenConfirmChannel( telemetry.IncrCounter(1, "ibc", "channel", "open-confirm") }() - 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]), - ), - }) - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), - ), - }) + EmitChannelOpenConfirmEvent(ctx, portID, channelID, channel) } // Closing Handshake @@ -527,16 +465,7 @@ func (k Keeper) ChanCloseInit( channel.State = types.CLOSED k.SetChannel(ctx, portID, channelID, 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]), - ), - }) + EmitChannelCloseInitEvent(ctx, portID, channelID, channel) return nil } @@ -601,16 +530,7 @@ func (k Keeper) ChanCloseConfirm( channel.State = types.CLOSED k.SetChannel(ctx, portID, channelID, 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]), - ), - }) + EmitChannelCloseConfirmEvent(ctx, portID, channelID, channel) return nil } diff --git a/modules/core/keeper/msg_server.go b/modules/core/keeper/msg_server.go index 8ad5cad5938..2e6800c25f5 100644 --- a/modules/core/keeper/msg_server.go +++ b/modules/core/keeper/msg_server.go @@ -33,24 +33,10 @@ func (k Keeper) CreateClient(goCtx context.Context, msg *clienttypes.MsgCreateCl return nil, err } - clientID, err := k.ClientKeeper.CreateClient(ctx, clientState, consensusState) - if err != nil { + if _, err = k.ClientKeeper.CreateClient(ctx, clientState, consensusState); err != nil { return nil, err } - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - clienttypes.EventTypeCreateClient, - sdk.NewAttribute(clienttypes.AttributeKeyClientID, clientID), - sdk.NewAttribute(clienttypes.AttributeKeyClientType, clientState.ClientType()), - sdk.NewAttribute(clienttypes.AttributeKeyConsensusHeight, clientState.GetLatestHeight().String()), - ), - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, clienttypes.AttributeValueCategory), - ), - }) - return &clienttypes.MsgCreateClientResponse{}, nil } @@ -67,13 +53,6 @@ func (k Keeper) UpdateClient(goCtx context.Context, msg *clienttypes.MsgUpdateCl return nil, err } - ctx.EventManager().EmitEvent( - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, clienttypes.AttributeValueCategory), - ), - ) - return &clienttypes.MsgUpdateClientResponse{}, nil } @@ -95,13 +74,6 @@ func (k Keeper) UpgradeClient(goCtx context.Context, msg *clienttypes.MsgUpgrade return nil, err } - ctx.EventManager().EmitEvent( - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, clienttypes.AttributeValueCategory), - ), - ) - return &clienttypes.MsgUpgradeClientResponse{}, nil } @@ -118,14 +90,6 @@ func (k Keeper) SubmitMisbehaviour(goCtx context.Context, msg *clienttypes.MsgSu return nil, sdkerrors.Wrap(err, "failed to process misbehaviour for IBC client") } - ctx.EventManager().EmitEvent( - sdk.NewEvent( - clienttypes.EventTypeSubmitMisbehaviour, - sdk.NewAttribute(clienttypes.AttributeKeyClientID, msg.ClientId), - sdk.NewAttribute(clienttypes.AttributeKeyClientType, misbehaviour.ClientType()), - ), - ) - return &clienttypes.MsgSubmitMisbehaviourResponse{}, nil } @@ -133,25 +97,10 @@ func (k Keeper) SubmitMisbehaviour(goCtx context.Context, msg *clienttypes.MsgSu func (k Keeper) ConnectionOpenInit(goCtx context.Context, msg *connectiontypes.MsgConnectionOpenInit) (*connectiontypes.MsgConnectionOpenInitResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - connectionID, err := k.ConnectionKeeper.ConnOpenInit(ctx, msg.ClientId, msg.Counterparty, msg.Version, msg.DelayPeriod) - if err != nil { + if _, err := k.ConnectionKeeper.ConnOpenInit(ctx, msg.ClientId, msg.Counterparty, msg.Version, msg.DelayPeriod); err != nil { return nil, sdkerrors.Wrap(err, "connection handshake open init failed") } - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - connectiontypes.EventTypeConnectionOpenInit, - sdk.NewAttribute(connectiontypes.AttributeKeyConnectionID, connectionID), - sdk.NewAttribute(connectiontypes.AttributeKeyClientID, msg.ClientId), - sdk.NewAttribute(connectiontypes.AttributeKeyCounterpartyClientID, msg.Counterparty.ClientId), - sdk.NewAttribute(connectiontypes.AttributeKeyCounterpartyConnectionID, msg.Counterparty.ConnectionId), - ), - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, connectiontypes.AttributeValueCategory), - ), - }) - return &connectiontypes.MsgConnectionOpenInitResponse{}, nil } @@ -164,29 +113,14 @@ func (k Keeper) ConnectionOpenTry(goCtx context.Context, msg *connectiontypes.Ms return nil, err } - connectionID, err := k.ConnectionKeeper.ConnOpenTry( + if _, err := k.ConnectionKeeper.ConnOpenTry( ctx, msg.PreviousConnectionId, msg.Counterparty, msg.DelayPeriod, msg.ClientId, targetClient, connectiontypes.ProtoVersionsToExported(msg.CounterpartyVersions), msg.ProofInit, msg.ProofClient, msg.ProofConsensus, msg.ProofHeight, msg.ConsensusHeight, - ) - if err != nil { + ); err != nil { return nil, sdkerrors.Wrap(err, "connection handshake open try failed") } - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - connectiontypes.EventTypeConnectionOpenTry, - sdk.NewAttribute(connectiontypes.AttributeKeyConnectionID, connectionID), - sdk.NewAttribute(connectiontypes.AttributeKeyClientID, msg.ClientId), - sdk.NewAttribute(connectiontypes.AttributeKeyCounterpartyClientID, msg.Counterparty.ClientId), - sdk.NewAttribute(connectiontypes.AttributeKeyCounterpartyConnectionID, msg.Counterparty.ConnectionId), - ), - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, connectiontypes.AttributeValueCategory), - ), - }) - return &connectiontypes.MsgConnectionOpenTryResponse{}, nil } @@ -206,22 +140,6 @@ func (k Keeper) ConnectionOpenAck(goCtx context.Context, msg *connectiontypes.Ms return nil, sdkerrors.Wrap(err, "connection handshake open ack failed") } - connectionEnd, _ := k.ConnectionKeeper.GetConnection(ctx, msg.ConnectionId) - - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - connectiontypes.EventTypeConnectionOpenAck, - sdk.NewAttribute(connectiontypes.AttributeKeyConnectionID, msg.ConnectionId), - sdk.NewAttribute(connectiontypes.AttributeKeyClientID, connectionEnd.ClientId), - sdk.NewAttribute(connectiontypes.AttributeKeyCounterpartyClientID, connectionEnd.Counterparty.ClientId), - sdk.NewAttribute(connectiontypes.AttributeKeyCounterpartyConnectionID, connectionEnd.Counterparty.ConnectionId), - ), - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, connectiontypes.AttributeValueCategory), - ), - }) - return &connectiontypes.MsgConnectionOpenAckResponse{}, nil } @@ -235,28 +153,12 @@ func (k Keeper) ConnectionOpenConfirm(goCtx context.Context, msg *connectiontype return nil, sdkerrors.Wrap(err, "connection handshake open confirm failed") } - connectionEnd, _ := k.ConnectionKeeper.GetConnection(ctx, msg.ConnectionId) - - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - connectiontypes.EventTypeConnectionOpenConfirm, - sdk.NewAttribute(connectiontypes.AttributeKeyConnectionID, msg.ConnectionId), - sdk.NewAttribute(connectiontypes.AttributeKeyClientID, connectionEnd.ClientId), - sdk.NewAttribute(connectiontypes.AttributeKeyCounterpartyClientID, connectionEnd.Counterparty.ClientId), - sdk.NewAttribute(connectiontypes.AttributeKeyCounterpartyConnectionID, connectionEnd.Counterparty.ConnectionId), - ), - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, connectiontypes.AttributeValueCategory), - ), - }) - 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 exection. +// 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) @@ -294,7 +196,7 @@ func (k Keeper) ChannelOpenInit(goCtx context.Context, msg *channeltypes.MsgChan // 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 exection. +// 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) @@ -332,7 +234,7 @@ func (k Keeper) ChannelOpenTry(goCtx context.Context, msg *channeltypes.MsgChann // 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 exection. +// 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) @@ -368,7 +270,7 @@ func (k Keeper) ChannelOpenAck(goCtx context.Context, msg *channeltypes.MsgChann // 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 exection. +// 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) @@ -424,13 +326,6 @@ func (k Keeper) ChannelCloseInit(goCtx context.Context, msg *channeltypes.MsgCha return nil, sdkerrors.Wrap(err, "channel handshake close init failed") } - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, channeltypes.AttributeValueCategory), - ), - }) - return &channeltypes.MsgChannelCloseInitResponse{}, nil } @@ -459,13 +354,6 @@ func (k Keeper) ChannelCloseConfirm(goCtx context.Context, msg *channeltypes.Msg return nil, sdkerrors.Wrap(err, "channel handshake close confirm failed") } - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, channeltypes.AttributeValueCategory), - ), - }) - return &channeltypes.MsgChannelCloseConfirmResponse{}, nil } From 402a3bf408c8e926f2fdc0aacb26fd53201d4476 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 7 Feb 2022 15:01:13 +0100 Subject: [PATCH 022/140] build(deps): bump github.com/cosmos/cosmos-sdk from 0.45.0 to 0.45.1 (#851) (#861) Bumps [github.com/cosmos/cosmos-sdk](https://github.com/cosmos/cosmos-sdk) from 0.45.0 to 0.45.1.
Release notes

Sourced from github.com/cosmos/cosmos-sdk's releases.

v0.45.1

This release introduces bug fixes and improvements on the Cosmos SDK v0.45 series:

Highlights

  • Added the missing iavl-cache-size config parameter parsing to set a desired IAVL cache size. The default value is way to small for big chains, and causes OOM failures.
  • Added a check in x/upgrade module's BeginBlock preventing accidental binary downgrades
  • Fix: the /cosmos/tx/v1beta1/txs/{hash} endpoint returns correct return code (404) for a non existing tx.

See the Cosmos SDK v0.45.1 Changelog for the exhaustive list of all changes and check other fixes in 0.45.x release series.

Full Diff: https://github.com/cosmos/cosmos-sdk/compare/v0.45.0...v0.45.1

Changelog

Sourced from github.com/cosmos/cosmos-sdk's changelog.

v0.45.1 - 2022-02-03

Bug Fixes

  • (grpc) #10985 The /cosmos/tx/v1beta1/txs/{hash} endpoint returns a 404 when a tx does not exist.
  • #10990 Fixes missing iavl-cache-size config parsing in GetConfig method.

Improvements

  • #10407 Added validation to x/upgrade module's BeginBlock to check accidental binary downgrades
  • #10768 Extra logging in in-place store migrations.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/cosmos/cosmos-sdk&package-manager=go_modules&previous-version=0.45.0&new-version=0.45.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
(cherry picked from commit c378ff3b9e637dbae171d2c5129d966b468a2305) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index cb79c90045a..639c5607ced 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alp require ( github.com/armon/go-metrics v0.3.10 github.com/confio/ics23/go v0.6.6 - github.com/cosmos/cosmos-sdk v0.45.0 + github.com/cosmos/cosmos-sdk v0.45.1 github.com/gogo/protobuf v1.3.3 github.com/golang/protobuf v1.5.2 github.com/gorilla/mux v1.8.0 diff --git a/go.sum b/go.sum index aeda86c4ec6..25a2e5dd4c4 100644 --- a/go.sum +++ b/go.sum @@ -202,8 +202,8 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cosmos/btcutil v1.0.4 h1:n7C2ngKXo7UC9gNyMNLbzqz7Asuf+7Qv4gnX/rOdQ44= github.com/cosmos/btcutil v1.0.4/go.mod h1:Ffqc8Hn6TJUdDgHBwIZLtrLQC1KdJ9jGJl/TvgUaxbU= -github.com/cosmos/cosmos-sdk v0.45.0 h1:DHD+CIRZ+cYgiLXuTEUL/aprnfPsWSwaww/fIZEsZlk= -github.com/cosmos/cosmos-sdk v0.45.0/go.mod h1:XXS/asyCqWNWkx2rW6pSuen+EVcpAFxq6khrhnZgHaQ= +github.com/cosmos/cosmos-sdk v0.45.1 h1:PY79YxPea5qlRLExRnzg8/rT1Scc8GGgRs22p7DX99Q= +github.com/cosmos/cosmos-sdk v0.45.1/go.mod h1:XXS/asyCqWNWkx2rW6pSuen+EVcpAFxq6khrhnZgHaQ= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= From 45a84f28e23bc387a0bb7715acdcc887e294ea34 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 7 Feb 2022 15:03:26 +0100 Subject: [PATCH 023/140] Added ChannelId to MsgChannelOpenInitResponse (#848) (#856) ## Description added ChannelId to MsgChannelOpenInitResponse this is needed for distribution within interchain security Also my go imports didn't like go metrics so I had to add the go-metrics alias Supersedes: https://github.com/cosmos/ibc-go/pull/839 Quote from @AdityaSripal Context for the other folks reviewing: This is a problem in general not just for CCV. But currently, in CCV we want to initiate a transfer channel and know what the channelID is. So if we want to do this as part of third-party module logic, it's currently impossible with the codebase. Since, if you try to use channelKeeper directly you will need portCapability; and a third party module will not have transfer's portCapability. So we have to use the MsgServer, but the response doesn't include the channelID which we need since then we want to send transfer packets over the established channel. So this is going to be an issue for any third-party module that wants to initialize a channel of a different IBC application and then use that channel to send packets over --- 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. - [x] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [x] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [x] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [x] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [x] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [x] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [x] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit 7b7eb9fa2b94f97600c2f8f19b93503d40e72588) Co-authored-by: frog power 4000 --- CHANGELOG.md | 1 + docs/ibc/proto-docs.md | 5 + modules/core/04-channel/types/tx.pb.go | 194 ++++++++++++++++--------- modules/core/keeper/msg_server.go | 7 +- proto/ibc/core/channel/v1/tx.proto | 4 +- 5 files changed, 137 insertions(+), 74 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df182d73b6a..adb0e0243ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### API Breaking +* (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. diff --git a/docs/ibc/proto-docs.md b/docs/ibc/proto-docs.md index a497c928af1..79d56a6a541 100644 --- a/docs/ibc/proto-docs.md +++ b/docs/ibc/proto-docs.md @@ -1888,6 +1888,11 @@ is called by a relayer on Chain A. MsgChannelOpenInitResponse defines the Msg/ChannelOpenInit response type. +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `channel_id` | [string](#string) | | | + + diff --git a/modules/core/04-channel/types/tx.pb.go b/modules/core/04-channel/types/tx.pb.go index 2d3c75345ce..6fcc1a44276 100644 --- a/modules/core/04-channel/types/tx.pb.go +++ b/modules/core/04-channel/types/tx.pb.go @@ -72,6 +72,7 @@ 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" yaml:"channel_id"` } func (m *MsgChannelOpenInitResponse) Reset() { *m = MsgChannelOpenInitResponse{} } @@ -107,6 +108,13 @@ func (m *MsgChannelOpenInitResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgChannelOpenInitResponse proto.InternalMessageInfo +func (m *MsgChannelOpenInitResponse) GetChannelId() string { + if m != nil { + return m.ChannelId + } + return "" +} + // 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. @@ -857,78 +865,79 @@ func init() { func init() { proto.RegisterFile("ibc/core/channel/v1/tx.proto", fileDescriptor_bc4637e0ac3fc7b7) } var fileDescriptor_bc4637e0ac3fc7b7 = []byte{ - // 1127 bytes of a gzipped FileDescriptorProto + // 1138 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0x4d, 0x6f, 0xdb, 0x46, - 0x13, 0xd6, 0x97, 0x65, 0x7b, 0xec, 0x37, 0xb6, 0x29, 0x7f, 0x28, 0x94, 0x2d, 0xfa, 0xe5, 0x21, - 0x11, 0x52, 0x44, 0x8c, 0x3f, 0x80, 0x22, 0x41, 0x2f, 0x92, 0x81, 0xa2, 0x41, 0xe1, 0xa6, 0xa0, - 0xdd, 0x1e, 0x8c, 0x02, 0x82, 0xb4, 0xda, 0x50, 0x84, 0x24, 0xae, 0x4a, 0x52, 0x4a, 0xf4, 0x0f, - 0x7a, 0xcc, 0xb9, 0xa7, 0xf4, 0xdc, 0x43, 0xfa, 0x33, 0x72, 0xcc, 0xa9, 0x2d, 0x7a, 0x20, 0x0a, - 0xfb, 0xd2, 0x33, 0x7f, 0x41, 0xc1, 0xe5, 0x92, 0xa2, 0x24, 0xb2, 0xa2, 0x92, 0xca, 0xcd, 0x6d, - 0x39, 0xf3, 0xec, 0xec, 0xec, 0xf3, 0x0c, 0x67, 0x97, 0x84, 0x7d, 0xb5, 0x81, 0x24, 0x44, 0x74, - 0x2c, 0xa1, 0x56, 0x5d, 0xd3, 0x70, 0x47, 0x1a, 0x1c, 0x49, 0xe6, 0xcb, 0x72, 0x4f, 0x27, 0x26, - 0xe1, 0x72, 0x6a, 0x03, 0x95, 0x1d, 0x6f, 0x99, 0x79, 0xcb, 0x83, 0x23, 0x7e, 0x5b, 0x21, 0x0a, - 0xa1, 0x7e, 0xc9, 0x19, 0xb9, 0x50, 0x5e, 0x18, 0x05, 0xea, 0xa8, 0x58, 0x33, 0x9d, 0x38, 0xee, - 0x88, 0x01, 0xfe, 0x1f, 0xb6, 0x92, 0x17, 0x96, 0x42, 0xc4, 0x9f, 0x92, 0xc0, 0x9d, 0x1b, 0xca, - 0x99, 0x6b, 0x7c, 0xd6, 0xc3, 0xda, 0x53, 0x4d, 0x35, 0xb9, 0x4f, 0x60, 0xb9, 0x47, 0x74, 0xb3, - 0xa6, 0x36, 0xf3, 0xc9, 0xc3, 0x64, 0x69, 0xb5, 0xca, 0xd9, 0x96, 0x70, 0x67, 0x58, 0xef, 0x76, - 0x9e, 0x88, 0xcc, 0x21, 0xca, 0x59, 0x67, 0xf4, 0xb4, 0xc9, 0x7d, 0x06, 0xcb, 0x2c, 0x68, 0x3e, - 0x75, 0x98, 0x2c, 0xad, 0x1d, 0xef, 0x97, 0x43, 0x36, 0x51, 0x66, 0x6b, 0x54, 0x33, 0x6f, 0x2d, - 0x21, 0x21, 0x7b, 0x53, 0xb8, 0x5d, 0xc8, 0x1a, 0xaa, 0xa2, 0x61, 0x3d, 0x9f, 0x76, 0x56, 0x92, - 0xd9, 0xd3, 0x93, 0x95, 0x1f, 0x5e, 0x0b, 0x89, 0xbf, 0x5e, 0x0b, 0x09, 0x71, 0x1f, 0xf8, 0xe9, - 0x14, 0x65, 0x6c, 0xf4, 0x88, 0x66, 0x60, 0xf1, 0xd7, 0x34, 0x6c, 0x8d, 0xbb, 0x2f, 0xf5, 0xe1, - 0x7c, 0x1b, 0xf8, 0x0a, 0x72, 0x3d, 0x1d, 0x0f, 0x54, 0xd2, 0x37, 0x6a, 0x2c, 0x2d, 0x67, 0x62, - 0x8a, 0x4e, 0x2c, 0xda, 0x96, 0xc0, 0xb3, 0x89, 0xd3, 0x20, 0x51, 0xde, 0xf2, 0xac, 0x2c, 0x83, - 0x71, 0x42, 0xd2, 0xf3, 0x13, 0x22, 0xc3, 0x36, 0x22, 0x7d, 0xcd, 0xc4, 0x7a, 0xaf, 0xae, 0x9b, - 0xc3, 0xda, 0x00, 0xeb, 0x86, 0x4a, 0xb4, 0x7c, 0x86, 0xa6, 0x23, 0xd8, 0x96, 0x50, 0x70, 0xd3, - 0x09, 0x43, 0x89, 0x72, 0x2e, 0x68, 0xfe, 0xd6, 0xb5, 0x72, 0xa7, 0x00, 0x3d, 0x9d, 0x90, 0xe7, - 0x35, 0x55, 0x53, 0xcd, 0xfc, 0xd2, 0x61, 0xb2, 0xb4, 0x5e, 0xdd, 0xb1, 0x2d, 0x61, 0xcb, 0xdb, - 0x98, 0xe7, 0x13, 0xe5, 0x55, 0xfa, 0x40, 0xab, 0xe0, 0x0a, 0xd6, 0x5d, 0x4f, 0x0b, 0xab, 0x4a, - 0xcb, 0xcc, 0x67, 0xe9, 0x66, 0xf8, 0xc0, 0x66, 0xdc, 0x6a, 0x1b, 0x1c, 0x95, 0xbf, 0xa0, 0x88, - 0x6a, 0xc1, 0xd9, 0x8a, 0x6d, 0x09, 0xb9, 0x60, 0x5c, 0x77, 0xb6, 0x28, 0xaf, 0xd1, 0x47, 0x17, - 0x19, 0x90, 0x7d, 0x39, 0x42, 0xf6, 0x02, 0xdc, 0x9d, 0xd2, 0xd5, 0x57, 0xfd, 0xb7, 0x29, 0xd5, - 0x2b, 0xa8, 0x3d, 0x9f, 0xea, 0xa7, 0x00, 0x53, 0x62, 0x07, 0x38, 0x09, 0x6a, 0xbc, 0x8a, 0x7c, - 0x6d, 0xaf, 0x60, 0x6f, 0x8c, 0xf7, 0x40, 0x08, 0x5a, 0xbf, 0x55, 0xd1, 0xb6, 0x84, 0x62, 0x88, - 0x40, 0xc1, 0x78, 0x3b, 0x41, 0xcf, 0xa8, 0x6e, 0x16, 0xa1, 0xfc, 0x11, 0xb8, 0x82, 0xd6, 0x4c, - 0x7d, 0xc8, 0x84, 0xdf, 0xb6, 0x2d, 0x61, 0x33, 0x28, 0x90, 0xa9, 0x0f, 0x45, 0x79, 0x85, 0x8e, - 0x9d, 0x77, 0xe7, 0x23, 0x93, 0xbd, 0x82, 0xda, 0xbe, 0xec, 0x3f, 0xa7, 0x60, 0x67, 0xdc, 0x7b, - 0x46, 0xb4, 0xe7, 0xaa, 0xde, 0xbd, 0x0d, 0xe9, 0x7d, 0x2a, 0xeb, 0xa8, 0x4d, 0xc5, 0x0e, 0xa1, - 0xb2, 0x8e, 0xda, 0x1e, 0x95, 0x4e, 0x41, 0x4e, 0x52, 0x99, 0x59, 0x08, 0x95, 0x4b, 0x11, 0x54, - 0x0a, 0x70, 0x10, 0x4a, 0x96, 0x4f, 0xe7, 0x8f, 0x49, 0xc8, 0x8d, 0x10, 0x67, 0x1d, 0x62, 0xe0, - 0xf9, 0xdb, 0xff, 0xfb, 0x91, 0x39, 0xbb, 0xed, 0x1f, 0x40, 0x21, 0x24, 0x37, 0x3f, 0xf7, 0x37, - 0x29, 0xd8, 0x9d, 0xf0, 0xdf, 0x62, 0x2d, 0x8c, 0x37, 0xd4, 0xf4, 0x7b, 0x36, 0xd4, 0xdb, 0x2d, - 0x87, 0x43, 0x28, 0x86, 0x13, 0xe6, 0x73, 0xfa, 0x2a, 0x05, 0xff, 0x3b, 0x37, 0x14, 0x19, 0xa3, - 0xc1, 0xd7, 0x75, 0xd4, 0xc6, 0x26, 0xf7, 0x18, 0xb2, 0x3d, 0x3a, 0xa2, 0x4c, 0xae, 0x1d, 0x17, - 0x42, 0x4f, 0x32, 0x17, 0xcc, 0x0e, 0x32, 0x36, 0x81, 0xfb, 0x1c, 0x36, 0xdd, 0x74, 0x11, 0xe9, - 0x76, 0x55, 0xb3, 0x8b, 0x35, 0x93, 0xd2, 0xbb, 0x5e, 0x2d, 0xd8, 0x96, 0xb0, 0x17, 0xdc, 0xd0, - 0x08, 0x21, 0xca, 0x1b, 0xd4, 0x74, 0xe6, 0x5b, 0xa6, 0x48, 0x4b, 0x2f, 0x84, 0xb4, 0x4c, 0x04, - 0x69, 0x7b, 0xb4, 0xe1, 0x8c, 0x18, 0xf1, 0xb9, 0xfa, 0x23, 0x05, 0x70, 0x6e, 0x28, 0x97, 0x6a, - 0x17, 0x93, 0xfe, 0xbf, 0x43, 0x54, 0x5f, 0xd3, 0x31, 0xc2, 0xea, 0x00, 0x37, 0xa3, 0x88, 0x1a, - 0x21, 0x3c, 0xa2, 0xbe, 0xf1, 0x2d, 0x0b, 0x25, 0xea, 0x4b, 0xe0, 0x34, 0xfc, 0xd2, 0xac, 0x19, - 0xf8, 0xfb, 0x3e, 0xd6, 0x10, 0xae, 0xe9, 0x18, 0x0d, 0x28, 0x69, 0x99, 0xea, 0x81, 0x6d, 0x09, - 0x77, 0xdd, 0x08, 0xd3, 0x18, 0x51, 0xde, 0x74, 0x8c, 0x17, 0xcc, 0xe6, 0x10, 0x19, 0xa3, 0x54, - 0xb7, 0xe9, 0xad, 0x94, 0x71, 0x3b, 0x6a, 0x57, 0xee, 0xa1, 0xcf, 0xcc, 0xcf, 0x34, 0x5a, 0xc3, - 0x1f, 0x03, 0xf3, 0x9f, 0xc2, 0x1a, 0x2b, 0x64, 0x27, 0x23, 0xd6, 0x0e, 0x76, 0x6d, 0x4b, 0xe0, - 0xc6, 0xaa, 0xdc, 0x71, 0x8a, 0xb2, 0xdb, 0x38, 0xdc, 0xdc, 0x17, 0xd9, 0x10, 0xc2, 0x25, 0x5b, - 0xfa, 0x50, 0xc9, 0xb2, 0xff, 0x78, 0x6e, 0x8f, 0x6b, 0xe3, 0x2b, 0xf7, 0x4b, 0x8a, 0x0a, 0x5a, - 0x41, 0x6d, 0x8d, 0xbc, 0xe8, 0xe0, 0xa6, 0x82, 0xe9, 0xab, 0xfd, 0x01, 0xd2, 0x95, 0x60, 0xa3, - 0x3e, 0x1e, 0xcd, 0x55, 0x4e, 0x9e, 0x34, 0x8f, 0xc4, 0x71, 0x26, 0x36, 0xa3, 0xc4, 0xa1, 0x4e, - 0x4f, 0x9c, 0x8a, 0xf3, 0xf0, 0x1f, 0x77, 0x6b, 0xf7, 0xab, 0x67, 0x82, 0x31, 0x8f, 0xd0, 0xe3, - 0x37, 0x2b, 0x90, 0x3e, 0x37, 0x14, 0xae, 0x0d, 0x1b, 0x93, 0xdf, 0x6e, 0xf7, 0x43, 0x49, 0x9c, - 0xfe, 0x82, 0xe2, 0xa5, 0x98, 0x40, 0x6f, 0x51, 0xae, 0x05, 0x77, 0x26, 0x3e, 0xb3, 0xee, 0xc5, - 0x08, 0x71, 0xa9, 0x0f, 0xf9, 0x72, 0x3c, 0x5c, 0xc4, 0x4a, 0xce, 0x4d, 0x2a, 0xce, 0x4a, 0x15, - 0xd4, 0x8e, 0xb5, 0x52, 0xe0, 0x46, 0xc9, 0x99, 0xc0, 0x85, 0xdc, 0x26, 0x1f, 0xc4, 0x88, 0xc2, - 0xb0, 0xfc, 0x71, 0x7c, 0xac, 0xbf, 0xaa, 0x06, 0x9b, 0x53, 0x97, 0xae, 0xd2, 0x8c, 0x38, 0x3e, - 0x92, 0x7f, 0x14, 0x17, 0xe9, 0xaf, 0xf7, 0x02, 0x72, 0xa1, 0x17, 0xa5, 0x38, 0x81, 0xbc, 0x7d, - 0x9e, 0xcc, 0x01, 0xf6, 0x17, 0xfe, 0x0e, 0x20, 0x70, 0x9b, 0x10, 0xa3, 0x42, 0x8c, 0x30, 0xfc, - 0x83, 0xd9, 0x18, 0x3f, 0xfa, 0x05, 0x2c, 0x7b, 0xe7, 0xaf, 0x10, 0x35, 0x8d, 0x01, 0xf8, 0xfb, - 0x33, 0x00, 0xc1, 0xda, 0x9b, 0x38, 0x61, 0xee, 0xcd, 0x98, 0xca, 0x70, 0xd1, 0xb5, 0x17, 0xde, - 0x15, 0x9d, 0x97, 0x77, 0xb2, 0x23, 0x46, 0x66, 0x39, 0x01, 0x8c, 0x7e, 0x79, 0x23, 0x3a, 0x46, - 0xf5, 0xe2, 0xed, 0x75, 0x31, 0xf9, 0xee, 0xba, 0x98, 0xfc, 0xf3, 0xba, 0x98, 0x7c, 0x75, 0x53, - 0x4c, 0xbc, 0xbb, 0x29, 0x26, 0x7e, 0xbf, 0x29, 0x26, 0xae, 0x1e, 0x2b, 0xaa, 0xd9, 0xea, 0x37, - 0xca, 0x88, 0x74, 0x25, 0x44, 0x8c, 0x2e, 0x31, 0x24, 0xb5, 0x81, 0x1e, 0x2a, 0x44, 0x1a, 0x9c, - 0x48, 0x5d, 0xd2, 0xec, 0x77, 0xb0, 0xe1, 0xfe, 0x46, 0x7a, 0x74, 0xfa, 0xd0, 0xfb, 0x93, 0x64, - 0x0e, 0x7b, 0xd8, 0x68, 0x64, 0xe9, 0x5f, 0xa4, 0x93, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x9b, - 0xb1, 0x4b, 0xd5, 0xd4, 0x12, 0x00, 0x00, + 0x13, 0xd6, 0x87, 0x2d, 0xdb, 0x63, 0xbf, 0xb1, 0x4d, 0xf9, 0x43, 0xa1, 0x6c, 0xd1, 0x2f, 0x0f, + 0x89, 0x91, 0x22, 0x62, 0x6c, 0x07, 0x28, 0x12, 0xf4, 0x62, 0x09, 0x28, 0x6a, 0x14, 0x6e, 0x0a, + 0xda, 0xed, 0xc1, 0x28, 0x20, 0x48, 0xab, 0x0d, 0x45, 0x48, 0xe2, 0xaa, 0x24, 0xa5, 0x44, 0xff, + 0xa0, 0xc7, 0x9c, 0x7b, 0x4a, 0xcf, 0x3d, 0xa4, 0x3f, 0x23, 0xc7, 0x9c, 0xda, 0xa2, 0x07, 0xa2, + 0xb0, 0x2f, 0x3d, 0xf3, 0x17, 0x14, 0x5c, 0x2e, 0x3f, 0x24, 0x91, 0x15, 0x95, 0x54, 0x6e, 0x6e, + 0xe4, 0xcc, 0xb3, 0xb3, 0xb3, 0xcf, 0x33, 0x9c, 0xdd, 0x25, 0xec, 0xa9, 0x0d, 0x24, 0x21, 0xa2, + 0x63, 0x09, 0xb5, 0xea, 0x9a, 0x86, 0x3b, 0xd2, 0xe0, 0x48, 0x32, 0x5f, 0x96, 0x7b, 0x3a, 0x31, + 0x09, 0x97, 0x57, 0x1b, 0xa8, 0xec, 0x78, 0xcb, 0xcc, 0x5b, 0x1e, 0x1c, 0xf1, 0x5b, 0x0a, 0x51, + 0x08, 0xf5, 0x4b, 0xce, 0x93, 0x0b, 0xe5, 0x85, 0x20, 0x50, 0x47, 0xc5, 0x9a, 0xe9, 0xc4, 0x71, + 0x9f, 0x18, 0xe0, 0xff, 0x51, 0x33, 0x79, 0x61, 0x29, 0x44, 0xfc, 0x29, 0x0d, 0xdc, 0xb9, 0xa1, + 0x54, 0x5d, 0xe3, 0xb3, 0x1e, 0xd6, 0xce, 0x34, 0xd5, 0xe4, 0x3e, 0x81, 0xa5, 0x1e, 0xd1, 0xcd, + 0x9a, 0xda, 0x2c, 0xa4, 0x0f, 0xd2, 0x87, 0x2b, 0x15, 0xce, 0xb6, 0x84, 0x3b, 0xc3, 0x7a, 0xb7, + 0xf3, 0x54, 0x64, 0x0e, 0x51, 0xce, 0x39, 0x4f, 0x67, 0x4d, 0xee, 0x33, 0x58, 0x62, 0x41, 0x0b, + 0x99, 0x83, 0xf4, 0xe1, 0xea, 0xf1, 0x5e, 0x39, 0x62, 0x11, 0x65, 0x36, 0x47, 0x65, 0xe1, 0xad, + 0x25, 0xa4, 0x64, 0x6f, 0x08, 0xb7, 0x03, 0x39, 0x43, 0x55, 0x34, 0xac, 0x17, 0xb2, 0xce, 0x4c, + 0x32, 0x7b, 0x7b, 0xba, 0xfc, 0xc3, 0x6b, 0x21, 0xf5, 0xd7, 0x6b, 0x21, 0x25, 0xca, 0xc0, 0x4f, + 0xa6, 0x28, 0x63, 0xa3, 0x47, 0x34, 0x03, 0x73, 0x8f, 0x01, 0x58, 0xa8, 0x20, 0xdb, 0x6d, 0xdb, + 0x12, 0x36, 0xdd, 0x6c, 0x03, 0x9f, 0x28, 0xaf, 0xb0, 0x97, 0xb3, 0xa6, 0xf8, 0x6b, 0x16, 0x36, + 0x47, 0x83, 0x5e, 0xea, 0xc3, 0xd9, 0x96, 0xfd, 0x15, 0xe4, 0x7b, 0x3a, 0x1e, 0xa8, 0xa4, 0x6f, + 0xd4, 0x42, 0x19, 0x64, 0xe8, 0xc0, 0x92, 0x6d, 0x09, 0x3c, 0x1b, 0x38, 0x09, 0x12, 0xe5, 0x4d, + 0xcf, 0x5a, 0xf5, 0x52, 0x0a, 0xd3, 0x98, 0x9d, 0x9d, 0x46, 0x19, 0xb6, 0x10, 0xe9, 0x6b, 0x26, + 0xd6, 0x7b, 0x75, 0xdd, 0x1c, 0xd6, 0x06, 0x58, 0x37, 0x54, 0xa2, 0x15, 0x16, 0x68, 0x3a, 0x82, + 0x6d, 0x09, 0x45, 0x46, 0x48, 0x04, 0x4a, 0x94, 0xf3, 0x61, 0xf3, 0xb7, 0xae, 0xd5, 0xa1, 0xb6, + 0xa7, 0x13, 0xf2, 0xbc, 0xa6, 0x6a, 0xaa, 0x59, 0x58, 0x3c, 0x48, 0x1f, 0xae, 0x85, 0xa9, 0x0d, + 0x7c, 0xa2, 0xbc, 0x42, 0x5f, 0x68, 0xed, 0x5c, 0xc1, 0x9a, 0xeb, 0x69, 0x61, 0x55, 0x69, 0x99, + 0x85, 0x1c, 0x5d, 0x0c, 0x1f, 0x5a, 0x8c, 0x5b, 0xa3, 0x83, 0xa3, 0xf2, 0x17, 0x14, 0x51, 0x29, + 0x3a, 0x4b, 0xb1, 0x2d, 0x21, 0x1f, 0x8e, 0xeb, 0x8e, 0x16, 0xe5, 0x55, 0xfa, 0xea, 0x22, 0x43, + 0xc5, 0xb2, 0x14, 0x53, 0x2c, 0x45, 0xb8, 0x3b, 0xa1, 0xab, 0x57, 0x2b, 0xe2, 0x6f, 0x13, 0xaa, + 0x9f, 0xa2, 0xf6, 0x6c, 0xaa, 0x8f, 0x96, 0x5b, 0x26, 0x59, 0xb9, 0x71, 0x57, 0xb0, 0x3b, 0xc2, + 0x7b, 0x28, 0x04, 0xad, 0xfa, 0x8a, 0x68, 0x5b, 0x42, 0x29, 0x42, 0xa0, 0x70, 0xbc, 0xed, 0xb0, + 0x27, 0xa8, 0x9b, 0x79, 0x28, 0x7f, 0x04, 0xae, 0xa0, 0x35, 0x53, 0x1f, 0x32, 0xe1, 0xb7, 0x6c, + 0x4b, 0xd8, 0x08, 0x0b, 0x64, 0xea, 0x43, 0x51, 0x5e, 0xa6, 0xcf, 0xce, 0xb7, 0xf3, 0x91, 0xc9, + 0x7e, 0x8a, 0xda, 0xbe, 0xec, 0x3f, 0x67, 0x60, 0x7b, 0xd4, 0x5b, 0x25, 0xda, 0x73, 0x55, 0xef, + 0xde, 0x86, 0xf4, 0x3e, 0x95, 0x75, 0xd4, 0xa6, 0x62, 0x47, 0x50, 0x59, 0x47, 0x6d, 0x8f, 0x4a, + 0xa7, 0x20, 0xc7, 0xa9, 0x5c, 0x98, 0x0b, 0x95, 0x8b, 0x31, 0x54, 0x0a, 0xb0, 0x1f, 0x49, 0x96, + 0x4f, 0xe7, 0x8f, 0x69, 0xc8, 0x07, 0x88, 0x6a, 0x87, 0x18, 0x78, 0xf6, 0x4d, 0xe3, 0xfd, 0xc8, + 0x9c, 0xbe, 0x59, 0xec, 0x43, 0x31, 0x22, 0x37, 0x3f, 0xf7, 0x37, 0x19, 0xd8, 0x19, 0xf3, 0xdf, + 0x62, 0x2d, 0x8c, 0x36, 0xd4, 0xec, 0x7b, 0x36, 0xd4, 0xdb, 0x2d, 0x87, 0x03, 0x28, 0x45, 0x13, + 0xe6, 0x73, 0xfa, 0x2a, 0x03, 0xff, 0x3b, 0x37, 0x14, 0x19, 0xa3, 0xc1, 0xd7, 0x75, 0xd4, 0xc6, + 0x26, 0xf7, 0x04, 0x72, 0x3d, 0xfa, 0x44, 0x99, 0x5c, 0x3d, 0x2e, 0x46, 0xee, 0x64, 0x2e, 0x98, + 0x6d, 0x64, 0x6c, 0x00, 0xf7, 0x39, 0x6c, 0xb8, 0xe9, 0x22, 0xd2, 0xed, 0xaa, 0x66, 0x17, 0x6b, + 0x26, 0xa5, 0x77, 0xad, 0x52, 0xb4, 0x2d, 0x61, 0x37, 0xbc, 0xa0, 0x00, 0x21, 0xca, 0xeb, 0xd4, + 0x54, 0xf5, 0x2d, 0x13, 0xa4, 0x65, 0xe7, 0x42, 0xda, 0x42, 0x0c, 0x69, 0xbb, 0xb4, 0xe1, 0x04, + 0x8c, 0xf8, 0x5c, 0xfd, 0x91, 0x01, 0x38, 0x37, 0x94, 0x4b, 0xb5, 0x8b, 0x49, 0xff, 0xdf, 0x21, + 0xaa, 0xaf, 0xe9, 0x18, 0x61, 0x75, 0x80, 0x9b, 0x71, 0x44, 0x05, 0x08, 0x8f, 0xa8, 0x6f, 0x7c, + 0xcb, 0x5c, 0x89, 0xfa, 0x12, 0x38, 0x0d, 0xbf, 0x34, 0x6b, 0x06, 0xfe, 0xbe, 0x8f, 0x35, 0x84, + 0x6b, 0x3a, 0x46, 0x03, 0x4a, 0xda, 0x42, 0x65, 0xdf, 0xb6, 0x84, 0xbb, 0x6e, 0x84, 0x49, 0x8c, + 0x28, 0x6f, 0x38, 0xc6, 0x0b, 0x66, 0x73, 0x88, 0x4c, 0x50, 0xaa, 0x5b, 0xf4, 0x2c, 0xcb, 0xb8, + 0x0d, 0xda, 0x95, 0xbb, 0xe9, 0x33, 0xf3, 0x33, 0x8d, 0xd6, 0xf0, 0xc7, 0xc0, 0xfc, 0xa7, 0xb0, + 0xca, 0x0a, 0xd9, 0xc9, 0x88, 0xb5, 0x83, 0x1d, 0xdb, 0x12, 0xb8, 0x91, 0x2a, 0x77, 0x9c, 0xa2, + 0xec, 0x36, 0x0e, 0x37, 0xf7, 0x79, 0x36, 0x84, 0x68, 0xc9, 0x16, 0x3f, 0x54, 0xb2, 0xdc, 0x3f, + 0xee, 0xdb, 0xa3, 0xda, 0xf8, 0xca, 0xfd, 0x92, 0xa1, 0x82, 0x9e, 0xa2, 0xb6, 0x46, 0x5e, 0x74, + 0x70, 0x53, 0xc1, 0xf4, 0xd3, 0xfe, 0x00, 0xe9, 0x0e, 0x61, 0xbd, 0x3e, 0x1a, 0xcd, 0x55, 0x4e, + 0x1e, 0x37, 0x07, 0xe2, 0x38, 0x03, 0x9b, 0x71, 0xe2, 0x50, 0xa7, 0x27, 0xce, 0xa9, 0xf3, 0xf2, + 0x1f, 0x77, 0xeb, 0x3d, 0x7a, 0x57, 0x1a, 0x63, 0xcc, 0x23, 0xf4, 0xf8, 0xcd, 0x32, 0x64, 0xcf, + 0x0d, 0x85, 0x6b, 0xc3, 0xfa, 0xf8, 0x8d, 0xef, 0x7e, 0x24, 0x89, 0x93, 0xf7, 0x2e, 0x5e, 0x4a, + 0x08, 0xf4, 0x2f, 0x68, 0x2d, 0xb8, 0x33, 0x76, 0xcd, 0xba, 0x97, 0x20, 0xc4, 0xa5, 0x3e, 0xe4, + 0xcb, 0xc9, 0x70, 0x31, 0x33, 0x39, 0x27, 0xa9, 0x24, 0x33, 0x9d, 0xa2, 0x76, 0xa2, 0x99, 0x42, + 0x27, 0x4a, 0xce, 0x04, 0x2e, 0xe2, 0x34, 0xf9, 0x20, 0x41, 0x14, 0x86, 0xe5, 0x8f, 0x93, 0x63, + 0xfd, 0x59, 0x35, 0xd8, 0x98, 0x38, 0x74, 0x1d, 0x4e, 0x89, 0xe3, 0x23, 0xf9, 0x47, 0x49, 0x91, + 0xfe, 0x7c, 0x2f, 0x20, 0x1f, 0x79, 0x50, 0x4a, 0x12, 0xc8, 0x5b, 0xe7, 0xc9, 0x0c, 0x60, 0x7f, + 0xe2, 0xef, 0x00, 0x42, 0xa7, 0x09, 0x31, 0x2e, 0x44, 0x80, 0xe1, 0x1f, 0x4c, 0xc7, 0xf8, 0xd1, + 0x2f, 0x60, 0xc9, 0xdb, 0x7f, 0x85, 0xb8, 0x61, 0x0c, 0xc0, 0xdf, 0x9f, 0x02, 0x08, 0xd7, 0xde, + 0xd8, 0x0e, 0x73, 0x6f, 0xca, 0x50, 0x86, 0x8b, 0xaf, 0xbd, 0xe8, 0xae, 0xe8, 0x7c, 0xbc, 0xe3, + 0x1d, 0x31, 0x36, 0xcb, 0x31, 0x60, 0xfc, 0xc7, 0x1b, 0xd3, 0x31, 0x2a, 0x17, 0x6f, 0xaf, 0x4b, + 0xe9, 0x77, 0xd7, 0xa5, 0xf4, 0x9f, 0xd7, 0xa5, 0xf4, 0xab, 0x9b, 0x52, 0xea, 0xdd, 0x4d, 0x29, + 0xf5, 0xfb, 0x4d, 0x29, 0x75, 0xf5, 0x44, 0x51, 0xcd, 0x56, 0xbf, 0x51, 0x46, 0xa4, 0x2b, 0x21, + 0x62, 0x74, 0x89, 0x21, 0xa9, 0x0d, 0xf4, 0x50, 0x21, 0xd2, 0xe0, 0x44, 0xea, 0x92, 0x66, 0xbf, + 0x83, 0x0d, 0xf7, 0xe7, 0xd3, 0xa3, 0xc7, 0x0f, 0xbd, 0xff, 0x4f, 0xe6, 0xb0, 0x87, 0x8d, 0x46, + 0x8e, 0xfe, 0x7b, 0x3a, 0xf9, 0x3b, 0x00, 0x00, 0xff, 0xff, 0xc5, 0x64, 0xa7, 0xfd, 0x0a, 0x13, + 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1424,6 +1433,13 @@ func (m *MsgChannelOpenInitResponse) MarshalToSizedBuffer(dAtA []byte) (int, err _ = 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] = 0xa + } return len(dAtA) - i, nil } @@ -2241,6 +2257,10 @@ func (m *MsgChannelOpenInitResponse) Size() (n int) { } var l int _ = l + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } return n } @@ -2748,6 +2768,38 @@ func (m *MsgChannelOpenInitResponse) Unmarshal(dAtA []byte) error { 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 default: iNdEx = preIndex skippy, err := skipTx(dAtA[iNdEx:]) diff --git a/modules/core/keeper/msg_server.go b/modules/core/keeper/msg_server.go index 2e6800c25f5..9342c0ff069 100644 --- a/modules/core/keeper/msg_server.go +++ b/modules/core/keeper/msg_server.go @@ -3,7 +3,8 @@ package keeper import ( "context" - "github.com/armon/go-metrics" + metrics "github.com/armon/go-metrics" + "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -191,7 +192,9 @@ func (k Keeper) ChannelOpenInit(goCtx context.Context, msg *channeltypes.MsgChan // Write channel into state k.ChannelKeeper.WriteOpenInitChannel(ctx, msg.PortId, channelID, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.Channel.Counterparty, msg.Channel.Version) - return &channeltypes.MsgChannelOpenInitResponse{}, nil + return &channeltypes.MsgChannelOpenInitResponse{ + ChannelId: channelID, + }, nil } // ChannelOpenTry defines a rpc handler method for MsgChannelOpenTry. diff --git a/proto/ibc/core/channel/v1/tx.proto b/proto/ibc/core/channel/v1/tx.proto index 4f28418c9a2..7fc8f730e33 100644 --- a/proto/ibc/core/channel/v1/tx.proto +++ b/proto/ibc/core/channel/v1/tx.proto @@ -54,7 +54,9 @@ message MsgChannelOpenInit { } // MsgChannelOpenInitResponse defines the Msg/ChannelOpenInit response type. -message MsgChannelOpenInitResponse {} +message MsgChannelOpenInitResponse { + string channel_id = 1 [(gogoproto.moretags) = "yaml:\"channel_id\""]; +} // 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 From 37bbee18b8730770d8fea4a24bbe1891066fa009 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 8 Feb 2022 17:11:23 +0100 Subject: [PATCH 024/140] fix: ica host OnRecvPacket error acknowledgement (#885) (#886) (cherry picked from commit 31487bcd9b5817e56863d595a2b8cf2ddc7051ef) Co-authored-by: Damian Nolan --- modules/apps/27-interchain-accounts/host/ibc_module.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/apps/27-interchain-accounts/host/ibc_module.go b/modules/apps/27-interchain-accounts/host/ibc_module.go index be302e09151..630c4d44fc1 100644 --- a/modules/apps/27-interchain-accounts/host/ibc_module.go +++ b/modules/apps/27-interchain-accounts/host/ibc_module.go @@ -105,7 +105,7 @@ func (im IBCModule) OnRecvPacket( _ sdk.AccAddress, ) ibcexported.Acknowledgement { if !im.keeper.IsHostEnabled(ctx) { - return channeltypes.NewErrorAcknowledgement(types.ErrHostSubModuleDisabled.Error()) + return types.NewErrorAcknowledgement(types.ErrHostSubModuleDisabled) } txResponse, err := im.keeper.OnRecvPacket(ctx, packet) From 6511e9c2ce3176f9edb94511464f9c49cfb13d10 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 9 Feb 2022 11:25:04 +0100 Subject: [PATCH 025/140] chore: add defensive check to ensure metadata does not change when reopening an active channel (#847) (#887) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description closes: #795 --- 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. - [ ] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [ ] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit 482b7abb097488b3931637e43fa5a958e3af5e07) Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> --- .../controller/keeper/handshake.go | 16 ++- .../controller/keeper/handshake_test.go | 45 +++++++ .../host/keeper/handshake.go | 21 ++- .../host/keeper/handshake_test.go | 32 ++++- .../27-interchain-accounts/types/metadata.go | 15 +++ .../types/metadata_test.go | 121 ++++++++++++++++++ 6 files changed, 243 insertions(+), 7 deletions(-) diff --git a/modules/apps/27-interchain-accounts/controller/keeper/handshake.go b/modules/apps/27-interchain-accounts/controller/keeper/handshake.go index 5b1fc619f9e..26a90878abf 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/handshake.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/handshake.go @@ -1,6 +1,7 @@ package keeper import ( + "fmt" "strings" sdk "github.com/cosmos/cosmos-sdk/types" @@ -49,9 +50,20 @@ func (k Keeper) OnChanOpenInit( return err } - activeChannelID, found := k.GetOpenActiveChannel(ctx, connectionHops[0], portID) + activeChannelID, found := k.GetActiveChannelID(ctx, connectionHops[0], portID) if found { - return sdkerrors.Wrapf(icatypes.ErrActiveChannelAlreadySet, "existing active channel %s for portID %s", activeChannelID, portID) + channel, found := k.channelKeeper.GetChannel(ctx, portID, activeChannelID) + if !found { + panic(fmt.Sprintf("active channel mapping set for %s but channel does not exist in channel store", activeChannelID)) + } + + if channel.State == channeltypes.OPEN { + return sdkerrors.Wrapf(icatypes.ErrActiveChannelAlreadySet, "existing active channel %s for portID %s is already OPEN", activeChannelID, portID) + } + + if !icatypes.IsPreviousMetadataEqual(channel.Version, metadata) { + return sdkerrors.Wrap(icatypes.ErrInvalidVersion, "previous active channel metadata does not match provided version") + } } 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 index 0f1e1af41a0..61c8a092a81 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/handshake_test.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/handshake_test.go @@ -30,6 +30,51 @@ func (suite *KeeperTestSuite) TestOnChanOpenInit() { }, true, }, + { + "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: channeltypes.ORDERED, + Counterparty: counterparty, + ConnectionHops: []string{path.EndpointA.ConnectionID}, + Version: TestVersion, + } + + path.EndpointA.SetChannel(channel) + }, + true, + }, + { + "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) + + counterparty := channeltypes.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + closedChannel := channeltypes.Channel{ + State: channeltypes.CLOSED, + Ordering: channeltypes.ORDERED, + Counterparty: counterparty, + ConnectionHops: []string{path.EndpointA.ConnectionID}, + Version: TestVersion, + } + + path.EndpointA.SetChannel(closedChannel) + + // modify metadata + metadata.Version = "ics27-2" + + versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + + channel.Version = string(versionBytes) + }, + false, + }, { "invalid order - UNORDERED", func() { diff --git a/modules/apps/27-interchain-accounts/host/keeper/handshake.go b/modules/apps/27-interchain-accounts/host/keeper/handshake.go index 48b3570dd67..11a7c7a378e 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/handshake.go +++ b/modules/apps/27-interchain-accounts/host/keeper/handshake.go @@ -1,6 +1,7 @@ package keeper import ( + "fmt" "strings" sdk "github.com/cosmos/cosmos-sdk/types" @@ -47,8 +48,20 @@ func (k Keeper) OnChanOpenTry( return "", err } - if activeChannelID, found := k.GetOpenActiveChannel(ctx, connectionHops[0], counterparty.PortId); found { - return "", sdkerrors.Wrapf(icatypes.ErrActiveChannelAlreadySet, "existing active channel %s for portID %s", activeChannelID, portID) + activeChannelID, found := k.GetActiveChannelID(ctx, connectionHops[0], counterparty.PortId) + if found { + channel, found := k.channelKeeper.GetChannel(ctx, portID, activeChannelID) + if !found { + panic(fmt.Sprintf("active channel mapping set for %s but channel does not exist in channel store", activeChannelID)) + } + + if channel.State == channeltypes.OPEN { + return "", sdkerrors.Wrapf(icatypes.ErrActiveChannelAlreadySet, "existing active channel %s for portID %s is already OPEN", activeChannelID, portID) + } + + if !icatypes.IsPreviousMetadataEqual(channel.Version, metadata) { + return "", sdkerrors.Wrap(icatypes.ErrInvalidVersion, "previous active channel metadata does not match provided version") + } } // On the host chain the capability may only be claimed during the OnChanOpenTry @@ -83,9 +96,9 @@ func (k Keeper) OnChanOpenConfirm( } // 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, + // 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 + // and host will disagree on what the currently active channel is k.SetActiveChannelID(ctx, channel.ConnectionHops[0], channel.Counterparty.PortId, channelID) 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 index e920a55f0df..0cac2912ccb 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/handshake_test.go +++ b/modules/apps/27-interchain-accounts/host/keeper/handshake_test.go @@ -30,6 +30,36 @@ func (suite *KeeperTestSuite) TestOnChanOpenTry() { }, true, }, + { + "success - reopening closed active channel", + func() { + // create a new channel and set it in state + ch := channeltypes.NewChannel(channeltypes.CLOSED, channeltypes.ORDERED, channeltypes.NewCounterparty(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID), []string{path.EndpointA.ConnectionID}, TestVersion) + 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) + }, true, + }, + { + "invalid metadata - previous metadata is different", + func() { + // create a new channel and set it in state + ch := channeltypes.NewChannel(channeltypes.CLOSED, channeltypes.ORDERED, channeltypes.NewCounterparty(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID), []string{path.EndpointA.ConnectionID}, TestVersion) + 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) + + // modify metadata + metadata.Version = "ics27-2" + + versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + + path.EndpointA.ChannelConfig.Version = string(versionBytes) + }, false, + }, { "invalid order - UNORDERED", func() { @@ -140,7 +170,7 @@ func (suite *KeeperTestSuite) TestOnChanOpenTry() { 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.EndpointA.ConnectionID}, ibctesting.DefaultChannelVersion) - suite.chainB.GetSimApp().GetIBCKeeper().ChannelKeeper.SetChannel(suite.chainB.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointB.ChannelID, ch) + 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) diff --git a/modules/apps/27-interchain-accounts/types/metadata.go b/modules/apps/27-interchain-accounts/types/metadata.go index 67588e2d809..3a7eae51cdf 100644 --- a/modules/apps/27-interchain-accounts/types/metadata.go +++ b/modules/apps/27-interchain-accounts/types/metadata.go @@ -27,6 +27,21 @@ func NewMetadata(version, controllerConnectionID, hostConnectionID, accAddress, } } +// 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 { + var previousMetadata Metadata + if err := ModuleCdc.UnmarshalJSON([]byte(previousVersion), &previousMetadata); 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 func ValidateControllerMetadata(ctx sdk.Context, channelKeeper ChannelKeeper, connectionHops []string, metadata Metadata) error { if !isSupportedEncoding(metadata.Encoding) { diff --git a/modules/apps/27-interchain-accounts/types/metadata_test.go b/modules/apps/27-interchain-accounts/types/metadata_test.go index 1c53b8f7126..05a1b457c38 100644 --- a/modules/apps/27-interchain-accounts/types/metadata_test.go +++ b/modules/apps/27-interchain-accounts/types/metadata_test.go @@ -5,6 +5,127 @@ import ( ibctesting "github.com/cosmos/ibc-go/v3/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 encoding format", + func() { + metadata.Encoding = "invalid-encoding-format" + + 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 { + tc := tc + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path := ibctesting.NewPath(suite.chainA, suite.chainB) + suite.coordinator.SetupConnections(path) + + 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 From 6c9cf5214d2b11116990ec45a2285106d3302843 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 10 Feb 2022 12:23:10 +0100 Subject: [PATCH 026/140] bug: use custom ante handler to reject redundant transactions in simapp (#884) (#896) * add custom ante handler * add godoc and changelog entry Co-authored-by: Carlos Rodriguez (cherry picked from commit d5e2ba5c3dbf5f51134f3cceefaa9c96b4d608d1) Co-authored-by: Carlos Rodriguez --- CHANGELOG.md | 2 ++ testing/simapp/ante_handler.go | 56 ++++++++++++++++++++++++++++++++++ testing/simapp/app.go | 17 ++++++----- 3 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 testing/simapp/ante_handler.go diff --git a/CHANGELOG.md b/CHANGELOG.md index adb0e0243ea..e89c1e30eac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,6 +78,8 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### 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 + ## [v2.0.2](https://github.com/cosmos/ibc-go/releases/tag/v2.0.2) - 2021-12-15 ### Dependencies diff --git a/testing/simapp/ante_handler.go b/testing/simapp/ante_handler.go new file mode 100644 index 00000000000..8e3e1f069ec --- /dev/null +++ b/testing/simapp/ante_handler.go @@ -0,0 +1,56 @@ +package simapp + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + + channelkeeper "github.com/cosmos/ibc-go/v3/modules/core/04-channel/keeper" + ibcante "github.com/cosmos/ibc-go/v3/modules/core/ante" +) + +// HandlerOptions extend the SDK's AnteHandler options by requiring the IBC +// channel keeper. +type HandlerOptions struct { + ante.HandlerOptions + + IBCChannelkeeper channelkeeper.Keeper +} + +// NewAnteHandler creates a new ante handler +func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { + if options.AccountKeeper == nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "account keeper is required for AnteHandler") + } + if options.BankKeeper == nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "bank keeper is required for AnteHandler") + } + if options.SignModeHandler == nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for ante builder") + } + + var sigGasConsumer = options.SigGasConsumer + if sigGasConsumer == nil { + sigGasConsumer = ante.DefaultSigVerificationGasConsumer + } + + anteDecorators := []sdk.AnteDecorator{ + ante.NewSetUpContextDecorator(), + ante.NewRejectExtensionOptionsDecorator(), + ante.NewMempoolFeeDecorator(), + ante.NewValidateBasicDecorator(), + ante.NewTxTimeoutHeightDecorator(), + ante.NewValidateMemoDecorator(options.AccountKeeper), + ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), + ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper), + // SetPubKeyDecorator must be called before all signature verification decorators + ante.NewSetPubKeyDecorator(options.AccountKeeper), + ante.NewValidateSigCountDecorator(options.AccountKeeper), + ante.NewSigGasConsumeDecorator(options.AccountKeeper, sigGasConsumer), + ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler), + ante.NewIncrementSequenceDecorator(options.AccountKeeper), + ibcante.NewAnteDecorator(options.IBCChannelkeeper), + } + + return sdk.ChainAnteDecorators(anteDecorators...), nil +} diff --git a/testing/simapp/app.go b/testing/simapp/app.go index 7f361aa9b8a..890d7c4661d 100644 --- a/testing/simapp/app.go +++ b/testing/simapp/app.go @@ -494,13 +494,16 @@ func NewSimApp( // initialize BaseApp app.SetInitChainer(app.InitChainer) app.SetBeginBlocker(app.BeginBlocker) - anteHandler, err := ante.NewAnteHandler( - ante.HandlerOptions{ - AccountKeeper: app.AccountKeeper, - BankKeeper: app.BankKeeper, - SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), - FeegrantKeeper: app.FeeGrantKeeper, - SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + anteHandler, err := NewAnteHandler( + HandlerOptions{ + HandlerOptions: ante.HandlerOptions{ + AccountKeeper: app.AccountKeeper, + BankKeeper: app.BankKeeper, + SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), + FeegrantKeeper: app.FeeGrantKeeper, + SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + }, + IBCChannelkeeper: app.IBCKeeper.ChannelKeeper, }, ) if err != nil { From 45b8e927ddcddec69d6eabc58c45e3e3d86d841e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 10 Feb 2022 14:11:45 +0100 Subject: [PATCH 027/140] refactor: allow the mock module to be used multiple times as base ibc application in middleware stack (backport #892) (#899) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: allow the mock module to be used multiple times as base ibc application in middleware stack (#892) ## Description Currently the `AppModule` assumes a single scoped keeper. This doesn't allow the mock module to be used as a base application for different middleware stack (ica stack, fee stack, etc) I broke the API because I think it is cleaner. If we want this to be non API breaking, I can try to readjust ref: #891 --- 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. - [ ] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [ ] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit 8f62a47a28c9190a8e54782fdfa2a45b085a7b4a) # Conflicts: # CHANGELOG.md * fix conflicts Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> --- CHANGELOG.md | 2 +- testing/README.md | 1 + testing/mock/ibc_app.go | 12 ++++++++++++ testing/mock/ibc_module.go | 22 +++++++++++----------- testing/mock/mock.go | 20 +++++++++++--------- testing/simapp/app.go | 6 +++--- 6 files changed, 39 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e89c1e30eac..6c67ce543ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,7 +53,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (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. ### State Machine Breaking diff --git a/testing/README.md b/testing/README.md index 97f540d5c4c..ad02a80e640 100644 --- a/testing/README.md +++ b/testing/README.md @@ -296,6 +296,7 @@ The mock module may also be leveraged to act as a base application in the instan 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 diff --git a/testing/mock/ibc_app.go b/testing/mock/ibc_app.go index a3f2db3bc6d..15f77d02d5a 100644 --- a/testing/mock/ibc_app.go +++ b/testing/mock/ibc_app.go @@ -2,6 +2,7 @@ package mock import ( sdk "github.com/cosmos/cosmos-sdk/types" + capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" @@ -10,6 +11,9 @@ import ( // MockIBCApp contains IBC application module callbacks as defined in 05-port. type MockIBCApp struct { + PortID string + ScopedKeeper capabilitykeeper.ScopedKeeper + OnChanOpenInit func( ctx sdk.Context, order channeltypes.Order, @@ -81,3 +85,11 @@ type MockIBCApp struct { relayer sdk.AccAddress, ) error } + +// NewMockIBCApp returns a MockIBCApp. An empty PortID indicates the mock app doesn't bind/claim ports. +func NewMockIBCApp(portID string, scopedKeeper capabilitykeeper.ScopedKeeper) *MockIBCApp { + return &MockIBCApp{ + PortID: portID, + ScopedKeeper: scopedKeeper, + } +} diff --git a/testing/mock/ibc_module.go b/testing/mock/ibc_module.go index fb46864709b..1ea1d3850ad 100644 --- a/testing/mock/ibc_module.go +++ b/testing/mock/ibc_module.go @@ -6,7 +6,6 @@ import ( "strconv" sdk "github.com/cosmos/cosmos-sdk/types" - capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" @@ -16,15 +15,16 @@ import ( // IBCModule implements the ICS26 callbacks for testing/mock. type IBCModule struct { - IBCApp *MockIBCApp // base application of an IBC middleware stack - scopedKeeper capabilitykeeper.ScopedKeeper + appModule *AppModule + IBCApp *MockIBCApp // base application of an IBC middleware stack } // NewIBCModule creates a new IBCModule given the underlying mock IBC application and scopedKeeper. -func NewIBCModule(app *MockIBCApp, scopedKeeper capabilitykeeper.ScopedKeeper) IBCModule { +func NewIBCModule(appModule *AppModule, app *MockIBCApp) IBCModule { + appModule.ibcApps = append(appModule.ibcApps, app) return IBCModule{ - IBCApp: app, - scopedKeeper: scopedKeeper, + appModule: appModule, + IBCApp: app, } } @@ -39,7 +39,7 @@ func (im IBCModule) OnChanOpenInit( } // Claim channel capability passed back by IBC module - if err := im.scopedKeeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + if err := im.IBCApp.ScopedKeeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { return err } @@ -56,7 +56,7 @@ func (im IBCModule) OnChanOpenTry( } // Claim channel capability passed back by IBC module - if err := im.scopedKeeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + if err := im.IBCApp.ScopedKeeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { return "", err } @@ -107,7 +107,7 @@ func (im IBCModule) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, re // set state by claiming capability to check if revert happens return capName := GetMockRecvCanaryCapabilityName(packet) - if _, err := im.scopedKeeper.NewCapability(ctx, capName); err != nil { + if _, err := im.IBCApp.ScopedKeeper.NewCapability(ctx, capName); err != nil { // application callback called twice on same packet sequence // must never occur panic(err) @@ -129,7 +129,7 @@ func (im IBCModule) OnAcknowledgementPacket(ctx sdk.Context, packet channeltypes } capName := GetMockAckCanaryCapabilityName(packet) - if _, err := im.scopedKeeper.NewCapability(ctx, capName); err != nil { + if _, err := im.IBCApp.ScopedKeeper.NewCapability(ctx, capName); err != nil { // application callback called twice on same packet sequence // must never occur panic(err) @@ -145,7 +145,7 @@ func (im IBCModule) OnTimeoutPacket(ctx sdk.Context, packet channeltypes.Packet, } capName := GetMockTimeoutCanaryCapabilityName(packet) - if _, err := im.scopedKeeper.NewCapability(ctx, capName); err != nil { + if _, err := im.IBCApp.ScopedKeeper.NewCapability(ctx, capName); err != nil { // application callback called twice on same packet sequence // must never occur panic(err) diff --git a/testing/mock/mock.go b/testing/mock/mock.go index fd454aa80d9..a37536de37a 100644 --- a/testing/mock/mock.go +++ b/testing/mock/mock.go @@ -8,7 +8,6 @@ import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" - capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" "github.com/gorilla/mux" "github.com/grpc-ecosystem/grpc-gateway/runtime" @@ -89,15 +88,14 @@ func (AppModuleBasic) GetQueryCmd() *cobra.Command { // AppModule represents the AppModule for the mock module. type AppModule struct { AppModuleBasic - scopedKeeper capabilitykeeper.ScopedKeeper - portKeeper PortKeeper + ibcApps []*MockIBCApp + portKeeper PortKeeper } // NewAppModule returns a mock AppModule instance. -func NewAppModule(sk capabilitykeeper.ScopedKeeper, pk PortKeeper) AppModule { +func NewAppModule(pk PortKeeper) AppModule { return AppModule{ - scopedKeeper: sk, - portKeeper: pk, + portKeeper: pk, } } @@ -124,9 +122,13 @@ func (am AppModule) RegisterServices(module.Configurator) {} // InitGenesis implements the AppModule interface. func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) []abci.ValidatorUpdate { - // bind mock port ID - cap := am.portKeeper.BindPort(ctx, ModuleName) - am.scopedKeeper.ClaimCapability(ctx, cap, host.PortPath(ModuleName)) + for _, ibcApp := range am.ibcApps { + if ibcApp.PortID != "" { + // bind mock portID + cap := am.portKeeper.BindPort(ctx, ibcApp.PortID) + ibcApp.ScopedKeeper.ClaimCapability(ctx, cap, host.PortPath(ModuleName)) + } + } return []abci.ValidatorUpdate{} } diff --git a/testing/simapp/app.go b/testing/simapp/app.go index 890d7c4661d..a2b78e7d260 100644 --- a/testing/simapp/app.go +++ b/testing/simapp/app.go @@ -350,8 +350,8 @@ func NewSimApp( // 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(scopedIBCMockKeeper, &app.IBCKeeper.PortKeeper) - mockIBCModule := ibcmock.NewIBCModule(&ibcmock.MockIBCApp{}, scopedIBCMockKeeper) + mockModule := ibcmock.NewAppModule(&app.IBCKeeper.PortKeeper) + mockIBCModule := ibcmock.NewIBCModule(&mockModule, ibcmock.NewMockIBCApp(ibcmock.ModuleName, scopedIBCMockKeeper)) app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( appCodec, keys[icacontrollertypes.StoreKey], app.GetSubspace(icacontrollertypes.SubModuleName), @@ -369,7 +369,7 @@ func NewSimApp( icaModule := ica.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper) // initialize ICA module with mock module as the authentication module on the controller side - icaAuthModule := ibcmock.NewIBCModule(&ibcmock.MockIBCApp{}, scopedICAMockKeeper) + icaAuthModule := ibcmock.NewIBCModule(&mockModule, ibcmock.NewMockIBCApp("", scopedICAMockKeeper)) app.ICAAuthModule = icaAuthModule icaControllerIBCModule := icacontroller.NewIBCModule(app.ICAControllerKeeper, icaAuthModule) From 2641fa1cf21744a739f85533e6536ec19c943b21 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 10 Feb 2022 22:16:26 +0100 Subject: [PATCH 028/140] imp: support custom keys for testing (#893) (#902) --- CHANGELOG.md | 1 + testing/app.go | 18 +++++++++++------- testing/chain.go | 17 ++++++++++------- testing/coordinator.go | 2 -- 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c67ce543ca..0b690fe916f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements +* (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. diff --git a/testing/app.go b/testing/app.go index 1c5bbe7c88a..487e0569ba2 100644 --- a/testing/app.go +++ b/testing/app.go @@ -126,13 +126,17 @@ func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs // commit genesis changes app.Commit() - app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{ - ChainID: chainID, - Height: app.LastBlockHeight() + 1, - AppHash: app.LastCommitID().Hash, - ValidatorsHash: valSet.Hash(), - NextValidatorsHash: valSet.Hash(), - }}) + app.BeginBlock( + abci.RequestBeginBlock{ + Header: tmproto.Header{ + ChainID: chainID, + Height: app.LastBlockHeight() + 1, + AppHash: app.LastCommitID().Hash, + ValidatorsHash: valSet.Hash(), + NextValidatorsHash: valSet.Hash(), + }, + }, + ) return app } diff --git a/testing/chain.go b/testing/chain.go index 281782f7837..8dfe4b8fb27 100644 --- a/testing/chain.go +++ b/testing/chain.go @@ -56,18 +56,23 @@ type TestChain struct { Vals *tmtypes.ValidatorSet Signers []tmtypes.PrivValidator - senderPrivKey cryptotypes.PrivKey + // autogenerated sender private key + SenderPrivKey cryptotypes.PrivKey SenderAccount authtypes.AccountI } // NewTestChain initializes a new TestChain instance with a single validator set using a -// generated private key. It also creates a sender account to be used for delivering transactions. +// generated secp256k1 Tendermint private key. It also creates a sender BaseAccount to be used for +// delivering transactions. // // 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. func NewTestChain(t *testing.T, coord *Coordinator, chainID string) *TestChain { // generate validator private/public key privVal := mock.NewPV() @@ -113,7 +118,7 @@ func NewTestChain(t *testing.T, coord *Coordinator, chainID string) *TestChain { Codec: app.AppCodec(), Vals: valSet, Signers: signers, - senderPrivKey: senderPrivKey, + SenderPrivKey: senderPrivKey, SenderAccount: acc, } @@ -237,7 +242,6 @@ func (chain *TestChain) sendMsgs(msgs ...sdk.Msg) error { // number and updates the TestChain's headers. It returns the result and error if one // occurred. func (chain *TestChain) SendMsgs(msgs ...sdk.Msg) (*sdk.Result, error) { - // ensure the chain has the latest time chain.Coordinator.UpdateTimeForChain(chain) @@ -250,7 +254,7 @@ func (chain *TestChain) SendMsgs(msgs ...sdk.Msg) (*sdk.Result, error) { chain.ChainID, []uint64{chain.SenderAccount.GetAccountNumber()}, []uint64{chain.SenderAccount.GetSequence()}, - true, true, chain.senderPrivKey, + true, true, chain.SenderPrivKey, ) if err != nil { return nil, err @@ -357,7 +361,6 @@ func (chain *TestChain) ConstructUpdateTMClientHeaderWithTrustedHeight(counterpa header.TrustedValidators = trustedVals return header, nil - } // ExpireClient fast forwards the chain's block time by the provided amount of time which will @@ -468,7 +471,7 @@ func CreateSortedSignerArray(altPrivVal, suitePrivVal tmtypes.PrivValidator, // CreatePortCapability binds and claims a capability for the given portID if it does not // already exist. This function will fail testing on any resulting error. -// NOTE: only creation of a capbility for a transfer or mock port is supported +// NOTE: only creation of a capability for a transfer or mock port is supported // Other applications must bind to the port in InitGenesis or modify this code. func (chain *TestChain) CreatePortCapability(scopedKeeper capabilitykeeper.ScopedKeeper, portID string) { // check if the portId is already binded, if not bind it diff --git a/testing/coordinator.go b/testing/coordinator.go index 615bd830ced..be308c790d5 100644 --- a/testing/coordinator.go +++ b/testing/coordinator.go @@ -55,7 +55,6 @@ func (coord *Coordinator) IncrementTime() { 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. @@ -105,7 +104,6 @@ func (coord *Coordinator) SetupConnections(path *Path) { // are returned within a TestConnection struct. The function expects the connections to be // successfully opened otherwise testing will fail. func (coord *Coordinator) CreateConnections(path *Path) { - err := path.EndpointA.ConnOpenInit() require.NoError(coord.t, err) From 96ca59b29ca947771ef60b55ae34a6d30307b736 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 15 Feb 2022 13:21:05 +0100 Subject: [PATCH 029/140] chore: add ParsePacketFromEvents testing helper function (#904) (#919) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description ref: #891 --- 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. - [ ] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [ ] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit c27d5b5f26b55d07489cc02fd0b8ce9fa8218e9a) Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> --- CHANGELOG.md | 1 + modules/apps/transfer/keeper/relay_test.go | 8 +-- modules/apps/transfer/transfer_test.go | 33 ++++++------ testing/events.go | 60 ++++++++++++++++++++++ 4 files changed, 80 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b690fe916f..486d58d0c54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements +* (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. diff --git a/modules/apps/transfer/keeper/relay_test.go b/modules/apps/transfer/keeper/relay_test.go index 8e77c73db9e..9d03bbde962 100644 --- a/modules/apps/transfer/keeper/relay_test.go +++ b/modules/apps/transfer/keeper/relay_test.go @@ -186,12 +186,12 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() { // send coin from chainB to chainA, receive them, acknowledge them, and send back to chainB coinFromBToA := sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)) transferMsg := types.NewMsgTransfer(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, coinFromBToA, suite.chainB.SenderAccount.GetAddress().String(), suite.chainA.SenderAccount.GetAddress().String(), clienttypes.NewHeight(0, 110), 0) - _, err := suite.chainB.SendMsgs(transferMsg) + res, err := suite.chainB.SendMsgs(transferMsg) suite.Require().NoError(err) // message committed - // relay send packet - fungibleTokenPacket := types.NewFungibleTokenPacketData(coinFromBToA.Denom, coinFromBToA.Amount.String(), suite.chainB.SenderAccount.GetAddress().String(), suite.chainA.SenderAccount.GetAddress().String()) - packet := channeltypes.NewPacket(fungibleTokenPacket.GetBytes(), 1, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, clienttypes.NewHeight(0, 110), 0) + packet, err := ibctesting.ParsePacketFromEvents(res.GetEvents()) + suite.Require().NoError(err) + err = path.RelayPacket(packet) suite.Require().NoError(err) // relay committed diff --git a/modules/apps/transfer/transfer_test.go b/modules/apps/transfer/transfer_test.go index ec36af78ceb..5190cdc8d29 100644 --- a/modules/apps/transfer/transfer_test.go +++ b/modules/apps/transfer/transfer_test.go @@ -8,7 +8,6 @@ import ( "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" ibctesting "github.com/cosmos/ibc-go/v3/testing" ) @@ -56,13 +55,13 @@ func (suite *TransferTestSuite) TestHandleMsgTransfer() { // send from chainA to chainB msg := types.NewMsgTransfer(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, coinToSendToB, suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String(), timeoutHeight, 0) - - _, err := suite.chainA.SendMsgs(msg) + res, err := suite.chainA.SendMsgs(msg) suite.Require().NoError(err) // message committed + packet, err := ibctesting.ParsePacketFromEvents(res.GetEvents()) + suite.Require().NoError(err) + // relay send - fungibleTokenPacket := types.NewFungibleTokenPacketData(coinToSendToB.Denom, coinToSendToB.Amount.String(), suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String()) - packet := channeltypes.NewPacket(fungibleTokenPacket.GetBytes(), 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) err = path.RelayPacket(packet) suite.Require().NoError(err) // relay committed @@ -82,18 +81,18 @@ func (suite *TransferTestSuite) TestHandleMsgTransfer() { // 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) - - _, err = suite.chainB.SendMsgs(msg) + res, err = suite.chainB.SendMsgs(msg) suite.Require().NoError(err) // message committed - // relay send - // NOTE: fungible token is prefixed with the full trace in order to verify the packet commitment - fullDenomPath := types.GetPrefixedDenom(pathBtoC.EndpointB.ChannelConfig.PortID, pathBtoC.EndpointB.ChannelID, voucherDenomTrace.GetFullDenomPath()) - fungibleTokenPacket = types.NewFungibleTokenPacketData(voucherDenomTrace.GetFullDenomPath(), coinSentFromAToB.Amount.String(), suite.chainB.SenderAccount.GetAddress().String(), suite.chainC.SenderAccount.GetAddress().String()) - packet = channeltypes.NewPacket(fungibleTokenPacket.GetBytes(), 1, pathBtoC.EndpointA.ChannelConfig.PortID, pathBtoC.EndpointA.ChannelID, pathBtoC.EndpointB.ChannelConfig.PortID, pathBtoC.EndpointB.ChannelID, timeoutHeight, 0) + packet, err = ibctesting.ParsePacketFromEvents(res.GetEvents()) + suite.Require().NoError(err) + err = pathBtoC.RelayPacket(packet) suite.Require().NoError(err) // relay committed + // NOTE: fungible token is prefixed with the full trace in order to verify the packet commitment + fullDenomPath := types.GetPrefixedDenom(pathBtoC.EndpointB.ChannelConfig.PortID, pathBtoC.EndpointB.ChannelID, voucherDenomTrace.GetFullDenomPath()) + coinSentFromBToC := sdk.NewCoin(types.ParseDenomTrace(fullDenomPath).IBCDenom(), amount) balance = suite.chainC.GetSimApp().BankKeeper.GetBalance(suite.chainC.GetContext(), suite.chainC.SenderAccount.GetAddress(), coinSentFromBToC.Denom) @@ -106,14 +105,12 @@ func (suite *TransferTestSuite) TestHandleMsgTransfer() { // 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) - - _, err = suite.chainC.SendMsgs(msg) + res, err = suite.chainC.SendMsgs(msg) suite.Require().NoError(err) // message committed - // relay send - // NOTE: fungible token is prefixed with the full trace in order to verify the packet commitment - fungibleTokenPacket = types.NewFungibleTokenPacketData(fullDenomPath, coinSentFromBToC.Amount.String(), suite.chainC.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String()) - packet = channeltypes.NewPacket(fungibleTokenPacket.GetBytes(), 1, pathBtoC.EndpointB.ChannelConfig.PortID, pathBtoC.EndpointB.ChannelID, pathBtoC.EndpointA.ChannelConfig.PortID, pathBtoC.EndpointA.ChannelID, timeoutHeight, 0) + packet, err = ibctesting.ParsePacketFromEvents(res.GetEvents()) + suite.Require().NoError(err) + err = pathBtoC.RelayPacket(packet) suite.Require().NoError(err) // relay committed diff --git a/testing/events.go b/testing/events.go index 037a4c342e3..7828b42619f 100644 --- a/testing/events.go +++ b/testing/events.go @@ -2,6 +2,7 @@ package ibctesting import ( "fmt" + "strconv" sdk "github.com/cosmos/cosmos-sdk/types" @@ -56,6 +57,65 @@ func ParseChannelIDFromEvents(events sdk.Events) (string, error) { return "", fmt.Errorf("channel identifier event attribute not found") } +// ParsePacketFromEvents parses events emitted from a MsgRecvPacket and returns the +// acknowledgement. +func ParsePacketFromEvents(events sdk.Events) (channeltypes.Packet, error) { + for _, ev := range events { + if ev.Type == channeltypes.EventTypeSendPacket { + packet := channeltypes.Packet{} + for _, attr := range ev.Attributes { + + switch string(attr.Key) { + case channeltypes.AttributeKeyData: + packet.Data = attr.Value + + case channeltypes.AttributeKeySequence: + seq, err := strconv.ParseUint(string(attr.Value), 10, 64) + if err != nil { + return channeltypes.Packet{}, err + } + + packet.Sequence = seq + + case channeltypes.AttributeKeySrcPort: + packet.SourcePort = string(attr.Value) + + case channeltypes.AttributeKeySrcChannel: + packet.SourceChannel = string(attr.Value) + + case channeltypes.AttributeKeyDstPort: + packet.DestinationPort = string(attr.Value) + + case channeltypes.AttributeKeyDstChannel: + packet.DestinationChannel = string(attr.Value) + + case channeltypes.AttributeKeyTimeoutHeight: + height, err := clienttypes.ParseHeight(string(attr.Value)) + if err != nil { + return channeltypes.Packet{}, err + } + + packet.TimeoutHeight = height + + case channeltypes.AttributeKeyTimeoutTimestamp: + timestamp, err := strconv.ParseUint(string(attr.Value), 10, 64) + if err != nil { + return channeltypes.Packet{}, err + } + + packet.TimeoutTimestamp = timestamp + + default: + continue + } + } + + return packet, nil + } + } + return channeltypes.Packet{}, fmt.Errorf("acknowledgement event attribute not found") +} + // ParseAckFromEvents parses events emitted from a MsgRecvPacket and returns the // acknowledgement. func ParseAckFromEvents(events sdk.Events) ([]byte, error) { From f418799c351dbff8218cc2b65b4433ff49bfdcb0 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 15 Feb 2022 12:38:49 +0000 Subject: [PATCH 030/140] fix: correctly claim capability for mock module, handle genesis exports (#921) (#922) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description This contains two fixes: - the capability being claimed by the scoped keeper was incorrect (mock.ModuleName -> port ID) - the mock module wasn't accounting for non empty genesis state in capabilities (after genesis export, capability will create the bound ports so rebinding doesn't need to happen) 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. - [ ] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [ ] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit afa2d904a1e47054d8013100bd97adde8dbd2a63) Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> --- testing/mock/mock.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/testing/mock/mock.go b/testing/mock/mock.go index a37536de37a..b621a05e9f7 100644 --- a/testing/mock/mock.go +++ b/testing/mock/mock.go @@ -43,6 +43,7 @@ var _ porttypes.IBCModule = IBCModule{} // PortKeeper defines the expected IBC port keeper type PortKeeper interface { BindPort(ctx sdk.Context, portID string) *capabilitytypes.Capability + IsBound(ctx sdk.Context, portID string) bool } // AppModuleBasic is the mock AppModuleBasic. @@ -123,10 +124,10 @@ func (am AppModule) RegisterServices(module.Configurator) {} // InitGenesis implements the AppModule interface. func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) []abci.ValidatorUpdate { for _, ibcApp := range am.ibcApps { - if ibcApp.PortID != "" { + if ibcApp.PortID != "" && !am.portKeeper.IsBound(ctx, ibcApp.PortID) { // bind mock portID cap := am.portKeeper.BindPort(ctx, ibcApp.PortID) - ibcApp.ScopedKeeper.ClaimCapability(ctx, cap, host.PortPath(ModuleName)) + ibcApp.ScopedKeeper.ClaimCapability(ctx, cap, host.PortPath(ibcApp.PortID)) } } From 053e00eeb1cffc55aff2d9bf1af14e7eb32da4ee Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 15 Feb 2022 15:04:12 +0000 Subject: [PATCH 031/140] docs: update migration docs for upgrade proposal in relation to ICS27 (#920) (#923) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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. - [ ] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [ ] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit d31f92d9bf709f5550b75db5c70a3b44314d9781) Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> --- docs/migrations/v2-to-v3.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/docs/migrations/v2-to-v3.md b/docs/migrations/v2-to-v3.md index 710e273a954..9b7615974c0 100644 --- a/docs/migrations/v2-to-v3.md +++ b/docs/migrations/v2-to-v3.md @@ -28,6 +28,33 @@ The ICS4Wrapper should be the IBC Channel Keeper unless ICS 20 is being connecte ICS27 Interchain Accounts has been added as a supported IBC application of ibc-go. Please see the [ICS27 documentation](../app-modules/interchain-accounts/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, vm module.VersionMap) (module.VersionMap, error) { + // set ICS27 Host submodule params + app.ICAHostKeeper.SetParams(ctx, icahosttypes.Params{ + HostEnabled: true, + AllowMessages: []string{"/cosmos.bank.v1beta1.MsgSend", ...], + }) + + // set ICS27 Controller submodule params + app.ICAControllerKeeper.SetParams(ctx, icacontrollertypes.Params{ + ControllerEnabled: true, + }) + + ... + + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) + +``` + +The host and controller submodule params only need to be set if you integrate those submodules. +For example, if a chain chooses not to integrate a controller submodule, it does not need to set the controller params. + ## IBC Apps From f68514c3954169cddc80856655cdedf43ba480aa Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 17 Feb 2022 11:57:53 +0000 Subject: [PATCH 032/140] chore(ica): add trail of bits audit report (#903) (#944) * chore(ica): add trail of bits audit report * relocate the audit report for ICA Co-authored-by: Carlos Rodriguez (cherry picked from commit f442721c7ea7ce69a4aadaf24808b2b250d89628) Co-authored-by: Carlos Rodriguez --- docs/.vuepress/config.js | 14 +++++++------- .../interchain-accounts/active-channels.md | 0 .../Trail of Bits audit - Final Report.pdf | Bin 0 -> 1147519 bytes .../interchain-accounts/auth-modules.md | 0 .../interchain-accounts/integration.md | 0 .../interchain-accounts/overview.md | 0 .../interchain-accounts/parameters.md | 0 .../interchain-accounts/transactions.md | 0 docs/migrations/v2-to-v3.md | 2 +- 9 files changed, 8 insertions(+), 8 deletions(-) rename docs/{app-modules => apps}/interchain-accounts/active-channels.md (100%) create mode 100644 docs/apps/interchain-accounts/audits/Trail of Bits audit - Final Report.pdf rename docs/{app-modules => apps}/interchain-accounts/auth-modules.md (100%) rename docs/{app-modules => apps}/interchain-accounts/integration.md (100%) rename docs/{app-modules => apps}/interchain-accounts/overview.md (100%) rename docs/{app-modules => apps}/interchain-accounts/parameters.md (100%) rename docs/{app-modules => apps}/interchain-accounts/transactions.md (100%) diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 8a69e377fe3..8e83c7c7cd3 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -109,37 +109,37 @@ module.exports = { { title: "Interchain Accounts", directory: true, - path: "/app-modules", + path: "/apps", children: [ { title: "Overview", directory: false, - path: "/app-modules/interchain-accounts/overview.html" + path: "/apps/interchain-accounts/overview.html" }, { title: "Authentication Modules", directory: false, - path: "/app-modules/interchain-accounts/auth-modules.html" + path: "/apps/interchain-accounts/auth-modules.html" }, { title: "Active Channels", directory: false, - path: "/app-modules/interchain-accounts/active-channels.html" + path: "/apps/interchain-accounts/active-channels.html" }, { title: "Integration", directory: false, - path: "/app-modules/interchain-accounts/integration.html" + path: "/apps/interchain-accounts/integration.html" }, { title: "Parameters", directory: false, - path: "/app-modules/interchain-accounts/parameters.html" + path: "/apps/interchain-accounts/parameters.html" }, { title: "Transactions", directory: false, - path: "/app-modules/interchain-accounts/transactions.html" + path: "/apps/interchain-accounts/transactions.html" }, ] }, diff --git a/docs/app-modules/interchain-accounts/active-channels.md b/docs/apps/interchain-accounts/active-channels.md similarity index 100% rename from docs/app-modules/interchain-accounts/active-channels.md rename to docs/apps/interchain-accounts/active-channels.md diff --git a/docs/apps/interchain-accounts/audits/Trail of Bits audit - Final Report.pdf b/docs/apps/interchain-accounts/audits/Trail of Bits audit - Final Report.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3f6182e4e98e6b4fc587a8e165f734299f69adba GIT binary patch literal 1147519 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�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

xh$O@^I@xjC#7m1LQC)skF5=yU4nn6U$G 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�X6!sB5Txc9+wd%hF!Y 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

%w0*B{*Mi>(Z_)U2LK*vwXZ~)m$HaAW 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,*g|kIB_4_KG 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 z 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�_;Vj2W1eDi6xnPe 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 ZpzUlaAG~ZCjn3+3&mdTI<_5Xa6~4 zd}IEpF;P{|Q#I?yoORu|7iR@r^D?-+{KhG*=#M8IYlEfculM&j$CR+5J6Z8Jk77tf zaf?-Sbh_=$zG{lW1;uMy8cA&+6eNvov+Muvc!#8-UeLui&Hm;8xukx>XyJbyvwq@ z!5xW{`o@K+;1E(6y({`t{1MMap&$eU8k)Yir3POYOp#(#(c;A1w8@A)itR&C&Lx^O z3Fh$8doeQ*OgDBD)#(+%P)AwU27&6pz2C+LG%3OBm6_Kp8(F8)(M;)XIC*W<15H;U z*Ql&5+W!97X?mI`9DB(fNV&|**yd3Vr=eIyEQN%<*Fqwsr8@liGdv%3JUQ(h6e}~V z?e)GDh=c5uld)}rMdi%5UM^Bp1qu{##QWt7q#grWG>P zF?cI1M{mxE@6EvXCoAIibXe-J_sYgSQ=8zBTJR4h0{@X|Io{3kuv;41+k_JFXd4cN zxmiW$wi*vVL#g%@HwnFm-w$U@reT@G4#EQdegSkXg`;O4a^cP z%d78{#iWwe*Dt2$&$usTWhFxNF||>Vb`MTQ@8ZzZ&(yY(YGt_dV3nFosPnr}cfPc_ zIf%R3z$~=f>K9b^@)I!R&8A}{;l~9ne~JViOI%~3L9@C;g9XMRmi0;zjNlHQLw6XB zj=%{9(dW+d!LX6%G7-3^1_1{sIt-@&mW}=jQ0?wOjKD3+n%$HgZjvlGgJ~0 zML*?z^JBy~SQiCXM?}SiA}+nu)8kwPN3E1JSU~=&kL;Mt&N>e2KK0V|zD;(8c{y zDDF&?`6T3EDxee)ka@2gIbj?=!On>En;##ZtjX$;J);?QEGX18HjD(#qzGZPvCH|O zgFt3;IH}Gi{RRGZ6jNA2L2?P=D1a5}Z6wEnv0fjakZ0jY?{yR>!vRF*86$Z@drzjt-Ob_w(JI`_&4 zbdHoF)a#$bP*j+q1=rg*7Gyj0do~=JI~+^b^+deF+rv6GwCu#CS*^R^{d{+)onGPK z+|4{b^x@72({ChxI@WJ=J?&N%C2x539hU0Wa`G=L@KCJ&`bAp0Ona>O=6KOZ_cg+D zOC2}4K)rZ|nyUE;-Kz#zOHxmX0uk@vuELd1{c0N+g7RFmsFx7?EycL4WLSJi+yMB? zGByeOc(;yTH;p&z-s)HM1iR%H$Bvj1(j04R^_1!|X4Psr8&2kR4>oR6iHMRulhITKB5*$6szY9X^zddmit!5}#ZQPyNjz~8S#XN#5UPL0U>RxY}vem|)P zKdpKRP@sYx+eFU$TEJV2I`Z{K;+NmC9%;IO@X=GOD;E2{3(Yo%9;C227bExe#Y{KD zkVvj*8i~la*nOfZVGFvdq5JsSoW0``hRh6pN&A_|0^~K$H6pxvjGWwImb-jPzpwQ@ z@cY5-&`IcJ=r1?6Hv-pr8>(wWJRUD)ihcvcJ{Z8 zuoQ#`825``AjWcNzpa~76FzKB$NNa)`m8`jU%cFP5D2$HGAH>POHYA$b6|v}WWrE9 zYHx!a1v>7s`*B&3t9BksIPlyC?g2A}-@7;?K23cZeCX^-ii6EeI6v2}!VIx|A40an z29tbQQo>6<%y%Yt3Fk90787zUCDK#kPFD4Jv<_AuI~t#Z6%;uMetA!HVGE5_Zqy<; zIqQzLP3=kW^L9(Lypy3>`P^{R>3|7%yXtjn0dYf`0((If9VjM6w7=WQhmoO3dTUoE z)&DYr&nfhMlsoXqYQ#U+CDCL2zNw_8H~k{-w2b1T!`}41TLu(7ABE_}A+h2!h*UnM zylv;J&!ZJ>?I&+QP@ym2PF?2Fgw!;vip@?jJDmq8I} zd&|Y;k!JzFK$IEL`uq@x0{RISEO`hw&~s6M4RE-hTFg9PptWv&()35aajxrCB6&fR z?JBaF)aY(BIszhz@`x8wQ<|yBIQtgmnd6w&Z7VA~BdOrwR&mf*6o2ToYAPkhrAZ@? zr=rUmI!ddq+1K_~bkv$kLp>8ANiQVS+~f!=&XkG}phk%3ladb-Y^K0?+2EZkt;agB zct)h+B?L+PAPcFIql^#xf(`_auH_$$`@p0hR_}ajxxhMq9ri;DH~6Nwt&$*h(__U& zShB)N{~km@zwlcL3OMiE0S97Fj3dr9`3D1Y-H@MG;+|U0S>Xp$tRtAprzyJz!{=)| zdw6Bak2mBmw6MpISmj}3`#rELSVnOkULK>yzK`f-_rLMV^!=TfWjZ8zmZ7r3;Y7bXP~j;JK6Y|25Un+LH+P-Mz7qWi* zWr8656COJ7INtvq4!HeS;s(muz&UEpzwY~NT>Gkl({>TSX z1>Y}@1)gSKdlg%aq3)!G$)VAO@6?Zn_Xj1YD#nVUXC14}^h@(8sP|^k3-i&`#N5Y;g8 zHY1$u-|eh3C3D&>s~U$E76O53f>VO?75a=Bdz=kBg%eF%7;k8hwn|({ylLjm;BS5k z3pxwhjp({Cc*$7AWOFgCX1zB(L{Dj^yj!hvruTQQqdVzcEJ6;sxd(-r2d4=;D$=v` zdk(cb1#_tL=94}choM&ntqQAMKIQQ^WlgIPBPwT`hn?8BP9K-3ikzmJsMN?7#u5#L zs#ANLJxwee?dy!rN@IRw_p!brqa$N4Icz$7G{uJ2F;nJ4m?|xz=*wj&^wKnH3}Tfu zWbqs^hGOTf+C2}I#``=>Q%Uk{V6OC>MO=7bwvn^CmYr@Kqz?pvEv}0w^i{DAW6bYE z#=a*XO3?#dwAL!3goPZFKVX6IIStcxzPw0#t#G<(w_S@4)kkl#gV7nm4bPIRgm{(X z6SfjWfrOF=Yr|^WF+=aGV8EH00m7hA?@}S+ko=k&f_C6-mA0I|Psoa0cWBtCS;-^p z*)HvO{;}ul55Mw#WlJ!0Fxsr6Cr8o4!;j}~nt!&JQ2`fM=YC6`(cz(HDwU*SfFcF{ z2VCC4F@IT4PwXSX$ul=qCD7DO4Ecvvx?`4?tiI{Y=khEuz+*saydLmKDx))??$-Q2XAn zsPE2V?0|03G{iQCI|j$ziD1eIsYhzu?dq96Q-kk+v;lcUCcUtt8v8UK}v>S0cO-1t5;Rnm~8Yxktk@CeTB*UhuaiJIED@5XE=#^P0O z-FwgAd43R$NHn?s_1Spc|)`7hQ1XxwI}184>S)&Z=M^B+C~unT~-14ISWe-b)> zZan`#gwFpE-TY@;|C$DXZuzSj_}_)j|1=%{ys`fO9-{*oGxMJbV_;whXbwg|?l~Yj z9Y8?JZEXJQ9zjg##kDe)`-eZs=Y1lTSIGaDV}9|Qv!7CXSr01Oe};Ijgh5GUiG zas1hPRt5n6`iljza01Q=fZzcDAi&z00QBb1=QvoI=m6R8fOW6}PD2*JM*cy3{;Y=O zFC_%Xd#7Xjiyg7BGXHI5{`Cy!zfaCz=NVuafDHjY$_}tKZ2y2_On}X>{KcLCN{533 zAf`B3nE})IAI}1m5*_PbO6Omn0T`-(O!vR`0C4;Oj)?(Kg3b)+RR=IMR=|h|m;t9d z>mS_oZ(9Tm1+Yakk8viHsu_mBI7Lt;P=eWsvc#!l}6iD;; z+BZp;%l&UwbzH6s3MBbo6r>~wkV*4N?kJ|bOw^e*r%;E$2wla_xm zq7hAn)5Py52Ibg?Q^rY@k`$0+j#$zPChXS{Bxol@jNrykw-z$Sqf+uAOU5S{v)+xz zz)R_>;wFfbD76K2- z>TWZGy`vsU4Dw_Ye+;}OyM z5=>pk!^Kgvg_8`9@D$J7uDp7*%AzxkAMFwt!qQr2v(vG8So6yh=vK@kD{pdI19A2k#rHJ{Gt8=xM_gQQ|EHzRv~i_zQcO&HN1;7Ac0zynKeu1##@LB*d)ga><&qme1> zPt}Q8bJ_kM4>Mc!`5qo#V>iK0h=yB)JT>R1mpA-j0(dgd4VBIHAfguCgP6#XyW(840&ewlDm{ z^-&N$8HMh+;W6oZenE6n%Wd);X5@2_DQ4O;#1jRHd58-8U|@hvKf@@F4kbTrw!cpA zzxHW*kw*Ql+fD5Rv*p}2L}d>)1aXn|$GA`@RHuwHMNS8Qz%nQE#>fQ^I?=h;MS=)X zqKkn>>p?+kYOXSa3W>MkPQLF2qrb4Y98{Re5a!Ay-5rcCmEFfYHUh^7nlZV_4tzUS zC<`~A5HvS2u$@agv-it!E~+kIIh_%~z3+oO3VK^PIB4kOqhYs@baMc`v^ZgdA4cfpLcpJRb4_*gf zyktg!<6FLseRjwy)jdHw3@>n;l{x3Fzs^R$W5L_3=RIaS$~R$dpj*h^9--f*7#IUj zp8hxxJV!&bzmI|1G3PW5`6IdH5Wv0706(*< z-s4K2ak-J#JGZV~2_%zV(ApZGSYK)&rBd%I$@E%U3^rCBYsM&|cVuYAcR5*zGe6&+ z9@wICn(|_feo+;L7)G-4`Pv1k*bRryfiI_%D+*64+*v;oW2pb(A|ZPfP0pQ;HW+>H{{u&m;YuJtLEyLKeTP7n{silK8dpd0DYMu>i zlzB+q-Od!;b&-GgvMkpzpD2&GxaRP%W;d&A=anm(kr*2z&c zvz~p*-cqE?-cp>Lp_TA;a&pwyNlCHILt)Os$tYoKJucCpI_I37UdEb*UgADOCmi>X zOT6I4^{2s|cj%V3SH_n1j~Vl7IS!j@sp|~ty|)Rp8($|*?W3WWNv8{6r?g4CY@;*g zl-10`1W8{f#w+&e7+xE?h=&Xqqpy=-ivx$=6UHcCBZo?=p}Nrey5F6^FfY{4#F9l~`tpM|$`{ zq%`Rrw1V?>hh1wD*AzydRXjL!;b)L|T0yk0lXB{oH6Wac9(EdfTpLr;QcRIqWIyNy z<52Or?m@%EUsORyW9!m98k#X=B@W zT47`%xLM%-H?N_-g=*ob)d@?v>Sexm-#j340~HP?`Pb;Kfp@RgUMe%qo!aCL6&1SY zjcrX;x;Hm9hI`wFu5yRI!3N^+o6c{y&G7uYLw5w_rL6Cw3|!N!A_^}_F2b-~TS-fB zkr@L!#~~H!r0nqGKvX_^P~=1{hFCUVA*6(Hk)3xh+yt)850Z85QGCz(vA!>Y${}D2 zT@AdzvF*BN#-3gCK6|{wv8Jp_>Xg_x)BOe&W*nW!?|mW37@{)^UEz0Pt4`D!&N}d& z@F7wfV=^|W$E|m&=O5qOx7rtmmNQUEq+F!f^uLoXdQ5K6cuBl9QmHSOb`ioY(Jj1G z(^LIq41+3%Uq!kP6sxe9IbZ6;$3fP{?BsbK+NN&`U%maDX%l{X47!`%%K7t{!|Dg0 zlHBFwH=f?&jQvcR#y=Hu4sZXsn!cL;q&?!~T}=7>?v&Rpyj8VT&Hw(n<00cGWqKU??@nq zG6L*|a%mR%WYjXWWl4u+b4aVJymQkCN4N@ZUt$3niN@wv14H6)vxyF2cW}0vcNK?I zR>dm&BCiy?SZl(j-w11^0F$`5_F{ljN?UrTnU3BOk;k5It$U-y8&!PUrPwuUWznSG znHgYun& z-%)&t6I}LACSgu4sI^fG=8;8|NF@J-gdN@S`=Nennp2|oxgEAEkzLO6SQyYvH(DX0 zfnteSX6+AK++vbcRU%{ZpxodFca}IovcmoiQlJQX-GsrvSZEB(q3_SXXM--X zgM&;-wm^mXXOp;Jw1PIloK96BZDaK0iQ{pSk)y*a+~K^pYN##touk3;c*Kw~*f>hB zLDT9l>YBOWo~#*v$$&VcOU;z199R6=lHtB2Z(eF-YQNrQyQTz|Kl7IU(NZ)DtP1AdG7BwLc2( z=ns`Cd8B%IV{K??IP=c zRuq!!G`u;bJb+otg`k|Y(Z!~jvb1oLB>PS^*m~o}y#tj+!%#xx80-3aj0?Zs2j|VR z8KY&F619OIKL@HQonSpQz8rqApwTrhvniKiR4LuiQZuw$XZ~Wr^MVX9O;$8IE>u+0 zlm<#3vy2BM1*~6Y1*`=gBr2P&5c%C8$|owpEqD(-5mC_rF0R;G1jkMqJ>1#uEqP^9nxPg<(BU(B$!S&(o zphJR<=7f4xc(r17AnsqSU~Z0ilC2R$s=y&zqq9mH{si&pk(sARPmHb%<43z#GgNMkkR#zV$d5co3e ziXa`-H6zQ{pj3{Dh-q~y%j)QR4&IO&ms&XY7a9N|4bW+zR~5XMllYS6XFe-L;s;Ms zxAcj4VQmXqCL=iv1Gr3YBTESSN)o6)S6n@)riftr7xt(sokU4ZK+L_jQ2xxs4@2z7 z9(7>G`f&201ET!{B0?r1k&Adl6c>lWn+|29ToNDUeI=p@2Bb<i2itUng_LA*tqisEN0+;-aDl0lyO%hI zSC^1;5~D2@==dQo@-oD zp2-dFRwMbPDp-Kt+Gd#G*=M$Vh4GT?DJ=!moy0ae1u_{ho8VII(weI(B8B2iNkqCW zS?-@w-h=i4a$Llzvc5VJOGpgE%O4WI4rjo+-y0PU7U3F+KF=yUUxKS1>^0p;NYenp zRa*#Rl5u!bDz?WD^*K$jFJkBzuAOZM(wU*pr(gZOyF>KD#yk!Ze!_8PISB~lGI!#s z{8sWqXpszz(VrxBOST5oFeX(_Mm`FXsG0)_8=(Ea!a?c8%+?inYSFYi>z}vevbtmd zNwGXYYXHLTt1CC8BEn1M3dO8D$B`tQ+2dC>$nw_?Yt@s5vXf#7*8+nbDK*Mz3R~9c zz&Q~}c%(BjhB!!Ujrs`zVfQPH=RodNVABH3g_DT*%VxdUz00Sjsr5RwHJFc|Y-6Os zRM6-i6s&lWqF=wr;zh!bOo^K2K7-*t$=ief&SjkROIjPO23`>6LycA#x!0? z($P5!x5yjgH*Zb2M<@kXKA?3`igAWO!2g_>2Is)5*`Cv1mg$6yUa@yukq0Sph8(=f>v4U|#Sq+2O__;6nuOHa2ovd!?62Y0a@e*NyMS}l`HeoBJSR+@c{< zx=_dQk9m}HQ4wOSIbCB1d|@TqeTm4AOZW=xkd(%K}3iX`6G=Eqn@EN(S^r zr^6bC#h?j_HH&@)1#DwBaZFe)9R}i*3T5o*xzZAjBAiL7oTTb;IXQ%Yy9<_ETHoM8 z6e3j+#Mwxbauu4BMs-+B+moDq!ynhG7uA|OVk&-Z*Klk{Fg*i5DxQ@0?oV+S)ERFX zqHsusl+PE?J-LK^@7ex_hHq)ew_yV#xTz}sQ zjp^Yb>tgtBEJez?gTI*Y=qr6`MhuL0Y@k>HRu-m|8a6w2Rw|KK*7i)56Q^`~z zP0wg)dg+V#(5CW{cb9y2zd*-{WXAVIo$me7Y>ntks!fRFn)@xy{ZO<}C>=&BIPgH; zvAs}c#sm`o3m2?p$>WZZ&>CJI9R8Yc=mq-e^kg=d`xP=n(gcTWX`x(Y}O;ypjmf$!$G(k9nzE3?kuOj+t@giah8~ z;7>z+|9g(pPs}TU9WVBro(ema z8peshGB>;qrpg%lh76dn3do0y57}2su?nh>yX-|Xa(VEd7*XH5;GK*FSEXkKE7y9# zk*I>;&69{(O3Rd-gYskG7J`dPOb}_;Uesd@ajoi$j zRpNYeYcV5x=;PdJsJJ+?KTzu#Ncxt8G39%cd>yL8UHABU#^FAVw_~+^kF|XdZjT%U zD_0~{whxH@D+>S?*VJT^5n#q=A|)pNv1eS*ktw&~4*IK+li2s#wR3u)v}9?jS%XZ= zt-8O=zar~B81;=TLQJegs?2DEjEV{x#dsU%L?qYrOgR%|4)M^$ffIE^WPW6{wr}j3 z*u#D4miP|-iO#2Rk=14zq*{l5OTn*Rx>d@j8v_u+JKEPAy%u?=TAWPEmVI*JaZz;8 zFU^#H;W=YKG9xQwYQ``wBAO<1Dj8m>RReF%1j?h5k|m1@-3W^;J7cvz&yX*-{d4M0 zDKD9@4!qL@(dU<5@f~f2W4$#VnsHoLVQINDlRX=k1k+o=~KbBE3BmLPqZI=inm)+#6Yk%&sUR zOWsEr3jX^moa^j_$;n=E|4%Xn4jbou*gQt%*s9CySiH0CR9$pvV(o- zX>>gHs=4XzQ+DZH`CX6g*926whdeD8V&b3htmG-KpcVOvyg!sRO~w+`t~%c^tSazc zG%&kH2=4ucUgt zb`$zB3-fRR@3Va8of3$bize32?jz9sx-TaNEGQhcvIm_qBoExRR)VZ2oj(!`kIOpQ z$^jQ_BsgEoP;ktn%!s+VZgUXrwp8`|B;ajJi`CChcC>w^LbuKHJiWz329Dd+a;JQlj&M zEOb44WnN2-rfRcu%n*~C#z}pWM3j6EbMuV{%q-Ypbg)N;zG%Wmh~=@v+pNhgwxx3A zePgOWYJv%=Le%78`_T^wTxxeMtuZHloBesl$?R8Je)ZMgQ(0LUuhO78pW(kjB4d^N z2WK1J#C<>1ze{-#P&^x0yHgc0xr|o4U4G=YoX+xZw=lpG!%`hc^Q6I^#F~FJ_T7 zhd4}2wma{@v8u&iz`P{`{aMu5qwc7aka4@l*kk&w!S$r3avz<+H{44to9tei3z1gU z@5aLsoF1a_!X8SBFfd9b`#S|nRPAf^ju?wabSF}KZd^>dnAXeP-lnQm)IX`-&8+iAP-Z+$e`pYE#`Hs z*AldaGa9yGD(#EC@D2yqg9YP=vHtM;UEAT%ZMyhq!TBzZEl>)3NXptLgV^eK)jNBb%9$Mw2EPO*i(_MrrTXNbU7QBy3%Xi9fb^_$(|Ggh zM`MPC`+}?acp{(G(0-HQ(3b@7`p&j2H4}my{KbS;yiHjBC8#rer;32A;77bnID%x0 zz!OaW2|v`vbDG{xXTwp;8{O{%aJl}`dAm;ych9)4=TiHFGc|$SjPV;Ak_rO96**TB zD%2fiCupz`*50bY+nYG?+cd<4+=$?hvys?ik}WaI_$vcT2(Oz`ME31Bb0RXZn-c{Y zqI^c4cM807K2jo!YhGvy?;PBC=Lyq*n5rNYYDhe6k&XiOArU#pDncVFyafggE^%_8 z=kei>OMcwn>h2fucdf4#4_EAms@;wIoqkK{cDfcD{IVRfP~9gEkCCmn4YBqMPCrn< zpBsU9VyCr{yZJ2V6Zq#aNDT_aT!>06RKX&Co>y@JYY=!zhF&?dohBk=afucv&BxW? zxROAWRRiIjqu&`l{KAw`n-`U`WRGfhjo2TCd^-!Kbx&)Zme=$535hB^nj!ZS^8B*V z*QjD)LMB~CT)}vD-!EtoDdrq)m z$&(yzEo=|QZ^GxZ@$^uqR8sF%viGazCA&x$&?^lF8aBR>9=i2#fs?hA;h!R!9!UTe zP2!z^qdL%}G>;c|!2SgK>L`Lu9N`FFF%Ld5!YVapGY^16M%wy#?@8CdJcXeWqwKLK zoPmN<02+tnEga(aI=KfcJ|3*E`lHs=?ndIP*G=&ckDCm|*PHc=3f!%CP6nk0|Lp8# zoFdg@6@pqrf+W1q;V$5f(6%-kmO+w7qU)p`BN{qFJPTAxg( z>z{Hs9ySx`?Xq8oaOWYuLFV)4)`(?l)gHxkYMW;Jfka?=mK?T$T#T%!CF&0H7Q+;7MpV+|XalS;=y}AAVmTl#o z-1XeI2KQY{%3EzY62j-()N<49j81-Gzfpdn0ir|Su8{$usWBN!1<7u=DA7gf2-EV~ z(8pKD7Hv-dvkww)WMk-8h+REI-y5+6dv>~_4_+?!ug-{3{R)jr#7ighma-BIB9zhI zyTfKeP0uk6 z7zl5+IIDif#C7{ch&PNP1;(|;Rp`NF!{YW6h32bOrl=_IlW(W-HPpN;JqJgs@fnXXeR{)q_C~ju4pE{ zjN^CAF^OzdhMMOO=hdeYC-?3?>@sDsVQl;8`%Zo1!$uQYNy#cVnG!wFOh(92XkfE1 z#(3Cm4#>Efhy`GseE};7*33X|$hakWqx&UiM&?HFvP3v#@@bwLd9{~~*W(1lG*pRH zgP0`Vo|?-X_id|)`8(=Wo0vf+W_5BX)d+{id1MzyL1?dLEelgwcsM*&*1o$EUl?4) z6Do@P@zk_h^qIQcj71%PUmSQ_9;IvC3kW>A^p@c8s7%g6k_m~mrTIT2$DUwLAeSgH zJA9H7?j|CB&^+Kh^o^))ohdVQMNS;@^eR$R=WmBIg&2f`AIy2mD9Sm=8BEh$j#tX; zgat0X?cZAw>&-yD+_0X$)=3&&a%Q7*-$&ZMrg>Il$AD%u zL$2o(9*A4Zbw0Z{b*dekWD+aTW_uI}_U0sMNGw9>nF|JGyY7T-lzbm&XpbIjVv=Hf znU87ttiP@5S_3DC7<%0Owse_s(&m2E<>r*vAM!rwePXynyyM41Y;8ryH4*$u$Ij!H zvOE0N<9kHT5B9r5&?z+4b0+gqk%7TL#K^0>zm9j4HU?MX9njsw>#?A}JOBsi#F zn)kRD@`X-4FMKafsKPN6;#^Fo2V{0pAMWe4cetlBmRK#e6|(5UWk>TwNq&xx;i&Lk z1^xxs$4hh574A7do($UdNtev;VLjZF*4vN`*AeuNhm4X?hAz9OG$UH^a?;om)=aEj zUU#0KhSvM*o~*9Fq-ImX=<0{G5tBR1(pu!8>_s+c-#_*jDpY=|PHAFag;mLFHzJ14 z)WpaX=v9r--0-NN6|Yp!*)8h|epG~OK$;|^iG|%Y%;2!BV4+vIa?xtLx(a=XV8M>O zXL5sv#*1b-nNz%@qTvhzacD@f!2g^=Bp*v8rNy^Gw?L3xrM-R!5(!K7p$y!iy_%es~ zD^^!~t;4skvQR>94~g z_N@~j;`~*|Ys;65xR1^;9eJBA&R)Ma$OTrA{!|;5qyAQUb9rYXPk+9$VQ-LeMVc#= z7fLWgYv2d6iU3Y;l*n>aJXq$VrS0>K`1^PhPdUQs+aj4vPWJZ5lxc(=JoC_VcbP_7 z9KC70-|7KBK~O|%BSl0dXI?xNe1f?}KVdZ6vQJP9uG@y^%fXQ$(@tTD`-=kOGcaTfQB8L~ z45~}KUh+D{RbG}KGOi!n+vfO}RPL654jSx&eDW2{uFY~F{-&|@cpREjBq_=(^o9V$ zJ@wKQ({JTblSb_d>AJ+VlZ$-3@9IzG5D1wPb~zp1Kb;|op{>@2^JTv+qhY0_z2d*m zq=y751hTTR}B32XVJr0u>n91XG4XwLhKT|l=oBX&R^u2+H!d4~xF*fv$z zF_Jh6>Uf`Er_K!l3#DAGkIwg88;wS7`mVSoQS!kjqlGEU3J8wP8nG~q%&P2ha%oHz z9z_kBugu)wz?n13igIUbHS8+-jlRa>al8`lvx|ndZ-hohz&~XA#?@cFj^?#FA^bL8 z0@M4H+j_xOOQG#(H+qAS_7z^FBaAu$FW;$c7n)ymxI~QBDvF6tDq9bk34^?Ev07SA z2a8PJ=gUtOyG7k(n8I+me9ckoK56q2FVK*s27d|~rG5C#dD$grd6;E|aW&PGQHH^X z#^)iooXkQ`X)78SH&DczpqkZQb9oLwzKOA>+BxU;fU<3mX3qh}fup0tY6}u1k6T&I zbZxD!^J%Ix8qBL+{0tNc4Fx)XKXH!;>unMPM(N3)>NQuERrBd_@W8wFjclCQ6%vW} z&9MyR=!)A@8FGL>TFaVR3szcYpSk}rW)9-x!KVDg^~&{w{L1g+^{fYa8Po>DhNvG4 zl#MVwNQSMNX_TnYygF|fZr8xdA+awJBdevI&Ss|I#}J#1!Kul*57W#1Vpmf)jLgkf z=C#p`4tVuGFaLfUWBkh5pv@jH76TD1jyMGF0W;&L&{*ga9Rb)E$$WpIFuq zrI&+(&N$Rv1QuIVlr>Uv+06<2+>bPrma3S_yS;`uKARq7&hTT6WvmyQMoy0#u8j|2 zl#iC|4OJZy?zsqCl!7rPB{1q$Uv=30-XOgt8^!m2-@&FaAjzrkm0{uQksfpmyxD<_ zdgEgrc@Vzh{9({-f`-^$<=hyk9>4v=anu6tV_5#JWA^IyT3UVK3f|gchA=D8k6sfT z5jHT1DxzslRGj@+bviZ54Vrk}3Hf1UU&{A^qrjO@o@T>wlRSKUcimRHPLDLHqNx5a z-g8e{TX64z0{45_nloUFSI*cC7r5%tt4s0HN?K%944omc*=D&dR_<^ zPjHPFt{o+!(N&C1yKuQL?ZfsfVd;G5->>!;KhA_sLY|j+00$Pvk3>ge+L3L5^J|UF zryNK;KJZW<(!t`CF_B4UW!Ch|@U`7z4q$((XWqIw)K%qwJ>*O14s;qaOG+u$L5$OjvC{b3Sn{zsD}z}-i(yu+lN46!QN z=rs}iq=lrwjvX?aWr#{|KFIh;!|VGbSph641ul<* zqU#j5R;5IO{%wVZrPX!|Wa1Q@^H1N%T9^H8U(casJ0w{BtZz;=!2F(J1A>`SMe+=o zzq8Q5_Sg)XXk(Uyal5@k8JYo#IjVSTi_U0F4eTigLSvql2l7X zn@6F&8|ykA^H|cW3&JvSv(73gAR)2rXP@d)i8%&CDZ{F247GZP%qd6K3gdG+ z&1J)U<4f+LryT#$W`1z{7{zo4OYLT|VyE%)(fu1iMD$VP;toT(z0r}|H*+QjToLcf zYv%JC!NkMdAgE~ILc$wgL`E=dxf};PZ_kM>&I`6PJJ`F0zZZn4K#(-~G~+Ija2v^D z`DHZ1bE3m~j@X8zXAZUQ{l1Qb|8I`2)-dd$LGYFuPj_Eim%F`=^rwmLDi+F`YY|zg zYL^dhwTpgBZHAV`H13*AFSD13dN%3vv2+!$;+oVSyjvDgdJHvAP5MlQM`RPJT5L{Y zMNn50qhnMtkU6}xk$^akT$s|m_1M9d-1L%#QnjCw(0Ci!k`d7vtqYbO+sm`!`0m%o zqpvS_XBhK~GhjWx7v6IG9>;V17QA-v%TUO(y>~@`?grKo48TzcvCAhcRqXAx)oZMI zS_`F5oVS1bZ10FHKWkm$Nb!F}`BUtq9Om2aS$i!lT4eHgs$8@eIPt*2g&zH6&0&&r zE8;plQf^#y;7@e2ZL|E?c^$-Zv$0!O()2R*`soB=71S0U?4YFOn4Up(8o-U$%01i# za~AY{uv>nhP;zHSQ`ByP!;A35sK>3ez@v805rU#IgpR}IdU-hhE$E9`J zI5SklXe%~dpSCyao73AZsvFlV+uSO?U7bJo2~=gCDU@R10bn5S{MP4l#PI8wskp)V zL@`ZI&iU2~1_!f|IB6W-h&&W4$p^c#KQj`_wh791J=FEB1IN=aCXZad@NgZi(Z#^l ztS+AUx9iUUE;&2!8lA|Qksm;mAWP42x>KVwAwWUz;gffDvUBUYua<36!zg&?a!PhOtZt{XP6QA>%B7ArT5< zX0;6F+ZA}`Sxz~;WOp^5sdv(-hVbiCNU}vGuvMg78fv6@A`!o`X0m|9P6Y@lH4R=%C(7dDN zeM0rzr8@mrq`<`aN9g<)rtmLq^|$nrGq5ospcnm5=uFYX%*EQk@juiVpfE)6@BXL% z9XezAyA6mLfYpQz>?KSr%*>quQs)29PN+Zl;omg`pgrin8U5d5{?{B3{5?yG|1Qq{ z$GP~Y@A$uqoB@#3U&M?JFzf$+R{#0r){d4J0H_?AoM=`Mi z%+wz*2Cz>5d=B8F{xB*)y%HM$j&TC4(?2=dzdrb*tVg&#zR!#u00#rf)nx_7EF@RQMWMl^5 zH+Ddw6ay=O)Df@)x~!N1SPo#~0MrVg$NmfmP^`oX;BSETCV-#&`&CxJApR~B;{d2M zfC~exj~#%){;s70v}Mt8`~|iE8jkL-Dy{#AxVMa|Yf0O+0|W@3;O_43?(XjH?hXk7 zf;$9v3GNcy9fG^NLvTNnz2EM=`|bXEobT5eYmLFQs#R69R?T`A_g&ZD4l%INGQ4rK z-q>VJZ`Q}ZneW*C>3d`WYQ_MV;~4Nc7y-lIAJ+j53c$Mlgh8>eG19)lrvPl%8_?>n zYOyi^SS|)uAmN)^?0+Cv|2)RR3KZxM6CD#^zGG&5gI2Ksl&wDve5}Ct2WT#UCiR!I z?XN8spb_7kc7ON1aRA2v%O;TGueATH^j}5!Ym1ql9k|tR^#VL=f3^)13s4fkng_tR z{`vWDWh2DIWI_&0nOz|{8#PVR48900itaNDtR{0B}J z$KP0Z|J6Rn@GqRBf3?q5UJ43|Ls<~R`xMpU@gI|jQ1&rycdWU!rjjbsd}gWP+A@*W zR_z~hjr+FtoMrW0cu8Z+0J~f_fW14vMu9T$;L2I(YSw2f2fg>2TlSY)Jx&fc8sSUF zA^W`s^^GZew-R!@2UuGTTGbt;RnPTI6W)HOEMZ8fAiDX)LB2zU3<4qPLN%lMS%F7L zidg^NF~4{hg9C`n5eR6*c`v1!(_eWp&mnXyDXo3Iq$g}py~d+2A_kSi8quxc5j`n%B}Rcw4B?xc^<|| zZj9)op%hVs>)@ib1Z!f8c{5fLk zruVR<+W7EaSM)o8rN$sCrfw?^GbVs#Sz2z}&V0^>kyliFiVv+_4yS)mVhBPJ*tSrA zb#|3b-(8DQmHMVG`$6XyM~Pdx%lR-H(xwp6cv-sBUc-s4*DDC~3+7?_ESH<(evV1I zt?)fHrZ%&i?oo>m1>N1nGkF_VA_9D=%2@tW;70?fHa#7dN?)4Xpa%wtVjrkqM#hKYj=ls zz*1Km1xb2bKXUW1v)hkq8n6V?GlQLgslmdQ53KQQ2?-L5UXIqOm9J&JvO#4p4;;?v z))}5yS}e)jsh-<-#UHXgcU9wLem&%LARy}k{jFk1H1~<>G4wIy4mBd@g|M4r41uTT z(HW{mr?^4XkvdLnLw_DaSbX?Nb@j(J+D}Zmj|xxUXIdWa>##IlcAxRu!`sMc;(9B# zln4c3*+^Vp5_;B}gy(B5uOWg?C*i=X$hTHOb(}(#^ZI2nzz-vTi&6`suB+0Y!`TZC zz!DV>i+`JqOw1=a%`I#&qxAUleoS77_adp5k$-?lo3yzy&LYW5Y+V^6im>qe`-}|z zzV~Co(+~s1ief(p1*;RSdAn=JJQHIbCnZgzK^##6pce4ksU)>x*-6>@V}()%5oPO? z_R$KmMM*G=^0G+9p(UUy+1{yP9S~lM;~kKNeRcsaDkH)gB&`m#CR!S_*@mwUSe7{@ z652NSjlLj}K`0leV~pUD-Xd}n^SsT@As`#aG*a7fZG!)S3X)`qMEOpLqeqfcoP`{+ zBY>a0PM&M79(+fZsX{m+yehBD=I$`kM<<{{v}RP4ALfo6i%~&5;j0@o;?AUkJVj6Q z`w+n~&K9g_@}wleP1=?{0tH%*z~itBT>hM2ozN}Gxa4#118M8mN3R63KiNZ+Eq`X4 zf(^7i?e_W*A}EHi^^A@fSnj$xdL@!wrQ?1kkn?N#$h|#KIJ?uw{Y)%JJ1+hJk&!V0 z0~4|F#<)8CDf}DG6zz90P1TCVJIcvwQ~hp~Xs!VDMXfz>@%xcgnDf)7EU+_Xm6WPJtHcxrE1M)Z)*yq#glxmem4+otVMDoBavp%RG1xL+w!YH-n$ROPrWq1&l7 zp{@DqdWMk~=Pdz+`0b=_F?L%`=!-773F+gap)qDnD2-3(WC#k4)?7c}&Il|!y&x0( z-5~W*At>80gy}cCnV%L z5kGz7=}1ZjET_jowhg^?WQRm+4}|V1t8w+zK$YHQcq0DrpZU5e36~dqwV50`DeS(R z)dHQ*GD6i`2OVxc15ar+SZDreCrp7FGM!%X@}0(K?zUU%+Ezqd6L4drGA)G$kdyLl zZAkhru)`4gI&z1D#(66HgPQc%algVWLIf&=HNjt0qZ71e;r1CY)N^V>8=r&7#E)A|K2TLpN7cp`B%(;OeA8j=sbF^b(sII7ybnmxSouH^9MWlVF{(OJ+nDQer4 z`?;Lpu;JL_xTQNu*(P!Irk~Kds{O=TnQQc0_a!0V$mTKd>Z*atbnnWj%dp~M^R&HK zK5d$Es@MY=&&?p?X@1GR^36GCL(FmC_{1pXj(#+{c7fz8>-jaavoJ#Kmh0~R?u<&* zSMHk=f&FRE125*wgT0@oqi-h17RgNviAOa|^}70rg^T3iAHgMMn7T4w6_5d1!A>$!MP10 zA}z_nURac_$QNd)$FqFm09pbHXOfG_d*XCjQeVyx;t3GMESQk9SLfzes-Xq8NN;G1 zo)o34_JG7Tv4Zb=v^Mi>n0vJOPs=3w8O_4uigy+UrP5nNpr7dU9A;360Z`vvYWgWA zS-Rk|P{{sE^z?5fsjU`a`%IHUW~jFJA({2kS4Jn`{j#pM22>`sC%22XWSvzdz4MvA zrptC)(vHUfuKI>~JS~?#4NGY2Ya5fQuQxXC7>3>RKKVe*h5OWainGVUh+J{2m^?L`6w*o%jhCAGwgQc8F7l+n^Ek>*#n_i&C`F`OUGG9MGq&^UtH=yZ3ipsNd zi6RAG>!IsCFvyz4%fJrvY#v3w^cFqPNkg@NyFi_pb@T?1l;QX8mg&A=n-0fO|hl^&Frh~-_+PczxnT!4-6Z2OL zC!-c#1Qk%N&>=LtI`3M!FGjqo4`>gN48t3(1`fd^uEC8Y=S?7w?s98C)&_^=_7M!= zeM;LuHxRs5TxA|KU+*bAB)^ou)V{pO+fwQ)qKAcO@IPU*cnWp83u=?{L%fG1BKgI~ z(tgvwZk=%=>eY>zX~I#27xtUO+Uo(UiZ`=bVJ?%cwv?~iae%2ybTlz{Pg~iJXcybs zdl_T64$uesrZGDS+1>7E9z-$GWZxovCkyVpwsL=ktng8MbsPANEO~q2HE~J-&mu}> z89*16b;Zc+IvL+Tu*T*d_M08vE(*(j;N?35S$_>zoRv`qZWlYFpFQl$I`8V`u{}-F zgt?plIEWE)WTnEM&y7=v>^|h^EMARA=silvVhz+Q_Rsmz!}~cr=_U7or--#m(JQ`` zrtGTQYqlm8k(}9g88=LvrX{ymJR>k2PCh-Q2X>Rm`WY#nX3X6KuiQCtDz9OK5w^RT z`hjp847S~(Yk_cUU&|i3Y&&HuvFI(cRVk~yzzi^(UAhmo`;0=Kju;Es+4mWr`k z0>n2xmNi1ExJr2AAC1`5WPP1>cG%Pq?{vkq*L{#~a*lV0pfTzR1m`1(&mXDq+S?5q zFu5wr*CMtz8q^uvd#f*;Y?IgYerPMgw6`PPJtqXN>1hCe`pzn1@1GNJIN1dd2Ql)9 zeKq2{&MJ@#V2(u}v!j&P$yr#soHw?Aitqlcw5K7q9|Y5O3>>C*uppteflgB0nJjR$ z5TPv#mP#GSPiU&e(AJEQN*&FwbF^@3tOZS)W!)Vc8#i%vNVd^7pg-X2ng=b#AUI>l0JdC(JOq%%>GYYj(oKE6ak}tlE zLdR&uWX0l{WfGw*)uU}OC5UX0Qhl|y#ag0De3R87nO({nRjQYHf=04SW5a1fX+vn+ zEKsf2bOU39U}Mj}0u>bsI^x$>4i*h>q1}|)L6|d{PxQ<87k}SW-;nxWAK~ONyru6U z?SjR|LZR$}?prD+rmm=YAS0bWe^CDTUcsDYFC?LKMvn~ZT5YiV{HlMWP}bt{Ja{a_<9t zs&XU1Ti1lg$N7`4pOAB#P_24MefVCfg3&mh=iYf?HOhg!Alf;g<}0;CPOMnXwhr2c zEA(q(_ud+~o`s%)OqYY7Mx2ksGL`s2wxn-WW1BLrD!UWF)i!*9nLr{%UYXq~SR`|4{hyB%viy#{(JM_)*8jQ%T= zf#Lw4tW1&;n(-pMR0Qk7GOgfUgM=)2dN@tog!yiu)f^b@7vZg9Im|!Ur8?~F@zm<>Tvwk;}|Za3$d-l z9|`IhE2R5w+lU|DNs_Stp1x(tA-o9bY{j2XOztT27t=%G6}2dD&MVo>bvLLXslYqE z9A8-Ewf>R0tOooHT5cbARG~$bU-zity{-dJSmZ08y&>A=5+3SVSdgB|9nnc z;_KX94H|6)P33y`_qXhre;m>JCNg>EXI;I|*o7`5(9baE&Y&&WWO4MmbpDxO>wE9+ zINtff`)59{?_gBJIoAvIH)N0E47l-gxd}}^zQYr|XV{h#$d%h#2z=lf(CfcMo!#~M zuJhQ5H-%1UEdTPM1-HlD@11Yd=QX4CTl6~~h_}t$^;>pkH(2Z!j1(?tTD{<1Uhvm` z87vP_*5B`qJ#dH`KVcdvB!fNi*pBTMg4~1Ir-PP{f8OI*cZc%f+cmmlc);Fd+GK8s zsjHk9HeXkXhwnNZ&w24tKNb-81C7lD-JOblLeAqIRx8oz@P?Ork4<1{$cj*o3#k?b zuuap3K=cASex~I^7=d za0}vr8C|IGiaTJG39fyH_$eRBp=`WE6|R{JZXb$Y4quhM`-$)t@%dNN#}b04GpTR( z6_rRALjKX$9QPZ=DLsdWLqpMY920vov0pmVfR)^>Q;ETmsCu(>P$EO2$_0{^9OEf( zd}UBb#a_5I-0~4wX2AR!nhC;8t^nB?%9QMR*s`yz?>%Eq4@N_zD zyn$M%I-!=Nmqg{YJHti`qcQp;h1qUh)PnSt?&T7j`|2S;?^&{jouw|ZQD znT(9hpJOdWaGre}9%9_P;`Up+FTXJ9tX=2G~hB*Pw>6Ax2Pu0qbGXoex6xGY55%8;iFP}Hnv0s%MIbv zG`VTfI8+!+WU%7OcfGO+sOG;3gUn(|MRe8Di>IP_5wk&u$8|>Q@`NMB>ZDeY=iv>B z6Uh5aWK~Qsm-~3fY`#SdU|tR4E%aM0*0Bu&zlG_BF(|{NE$T|;cs1$N3Byl#mssj0 zM3duU;bzH2YO3@a)QIXva#Js0hf3u|4sb+=*L=yFSp3FTmR~KJDs9Dx?i+~ExbRAI zMTa}V$1&k7+&zSx&)%lSVBDHQ%6(s(Wx;c7m7%m^>QW+-GW=5}4sKQ_ao~2}GK{KV zMNd?gU3o?zJ!TdCxldbFP;TSHwr>Ix?3!&;9JaXPN-t9IE>HNs2$N3 z@Q0%TZ23=(B5NPQZW!0l?4~9>^g6ud5)9@mkBNaFE?exS(3mz4v>qR9(`XGx4;Ft` zy1+vV3TR0U;h_@pfc3YMYlWU7c? z#cNv}1}PP_p0O$$X*g~6J;gQ#$%7haqEh{DSQ-3RxMekz@GDh`@Tz%5whY1^s|FXg z70Tk9UMSq7SuJI=p__ufpxu)o-dSDVXTwZd4V=(^hkd1@oy8RSZ|M!&h9gOWRB*fB zQWJO3V2?+_I?Ys^+7po*P(9+jOHpo(%B1YfQIPuuRq7-;;7oMV?_0~5RF!3$JYA1S zYy=MDlNMK0f0Pyfq-kz;cuA4pLv1o8(DFVG@u1idN$!+xD6B55@>Ni&EN2-mUrg7? z5A4P8z&P#sH4;>xf4ohJk&+BCnuo~PafRqoFmaD%oG2Gk%F|c($zb=Hyy1>eCS$Cv zV0f{K?{Z-pJh=f>Es?+}ZOSl%b2V7PS>!d*Vwvv7*?Huvl5??AJFHRLx7E({&3q?M zuRY;mG@ZGQSHYwYY+zmxd$s%{2vIjE60Ez#<|#`?fa96wmv)z&AM}x?|A0wU@0Kf7 zSHy^*cD>Wy`-xpchT$eUisMWg`b-K=e;>q#DA1}QVZ81!Xb6-5*jtg(^E@hn<@ZcF z(UfC4#6PuXY-QBB)ueUw)%Hm{w=Bh;7wb?PxCBt@Ls1XqV+rAy$1|e!qgo{VGV|Dr zM7VEFSUOL#cw!)NY`5rQ0#iF=k};B0=7`~kQ@M}Fdxh63udCV1IP=LYr{$4wS{$klKJweUaHkH$v92P6iWnM?jf!uquXvHexAg*YvW=ux;F81a$ z0eOmZ{u>1B(TA}6EpwgfkZ{Puq`ClQ`D2(We%NQ^crZDZ~`=0s%E#`ru zkl2qJ^+{y}piugBZnEe4X8|nje8E#iKMb6r%PyCn*}|*~dQ=1GZ{c7orV|C`lEJ&cwF##xW`> zi@3^BUYM!KP<`X>hHdGH+iLyAm%n`EifsB&t-W53qpA~@jLl3G3u@;q5n5ic%WPV% zO}!4Ho#uB5bG7-9{G+%gX(RJYS;Jk3%~s-Hf^KN4I;dyF0UYJR%vTLEfgVChHTBaH>x}4&{Lm z%@C>clvB*}Gu1`Baf(ST{H~fhDEHYAjr!RH%JQexoFL8$DbZ?6RB9E6t}2bqYATRc zn2Kq|)M%!12LlMwHeVsoEVcG7wwjX6jaP@5kZ>sL8k~Wuc{Fv+FiYK3m9? z9+nO17fT@As;Z3pL+mlRTnPp+wf1Hsqe2!2LWfboKC29LCcaRbQ%ujLq$b_7A~4Y! zowby94PRnaXVLh{*ed&soJw{WQP!#o7l+40&}fJrYi4Zkyl~nY;j1$4jeftHOrx@^ zHDs{VoZ@*5)x7%=Y;GwcE9x6Mv*CO*IFDvLbG!Das!5Ixn?Iz*I;0T^1A(>3cI|E= zgZKrm35qw>PzC!`B0srSfZBXu%vS#ul>`Lr0c->8*e4=^5_R?Wbq?cUQkAkh)mHjd zSv6~F2v>7$MOlDIFb!ZV((vVh3Em$~sga!x#q@$1vb*=2yL#b!fQe^$G^CDl zbvq|>AC+xyv1tJE_AiBi?;p%)Bbhu+R)&k?k~yx2Lq#Mhhej(T<&qTwvZeQoG-t5N zk0ihw)R{^k(NF>v(b-9qm63llc3%dqj#R)m8>7|7`c_m}DvFuf+Dq!GcyeN`#qtyA zVLs`U9GSb>w(8L+S0DbSZ>0FqS)grRg(Fy{!NrMc>NJCfrs_!6&4C3IBvz3>yhyt>n1?SPm!fpn>zRY?@~<^|4UYL8AAZqK2HQLc-__9=1W zHp|jb1V%#UK+8kIf_Z zFrebuO=Bjpn!)da2qV=r>F{o)5}HVeubK;aflw=hn_{VwuAc8!Ih@b~0=a+T%znga zfhsg;)at6!Rr(k;_WO%igoBLB@A=QH1t*Q~c*%LHcTY+ii>aIVv{~8(<36<%CKNnM zGPyJ4GP5M3j68y#y7!$H684Ahse)VX{f3fox|CHK$j$vFbJv#>Wjq^*2V7tuPrQCPR8sfX)Cbq zbC+0J)NMw^Z$PN}&WU;7?fv8a{UOedzEL;5mMe){5?U3Tca?*~+ zWM#y!DTcNSTB^$l?S}=|&J1T0e&5mZol=wRN*g}B3uf5)Ro%xC$enedl5W4jR=Ek6 z+@z$T%iVDA(>A|6+;+M%JaQxw$J~@WzDTNW2IZW|W|~BA8TSzm{$n~dwOK#%p}q=I zCgk!&-gvB)s&xL3~^1LRSoHctu~OOyAqY^qUv1{Fo=X zU*>F%i=e{-qWwYcc7weABAim0a9b0twBl~bL4#Ae3a4V%1Liu+VaaqN0pQ0zGnz+h zNAvmts;2<-i|>_q7wWejJw|>+!dtk$UHUd|5`I*f{4{|YeP1`7tZv_LEH!3hTMY_v zjx{vGd*40@d7JfTwx*L^Hf!83bWtA~f-^VWo_*~2-T-l%n|H^X_x&--!tY9V%Ui_4 zuw${S6fNSJ2x+VL5_R{MTeL}^(4&&>!T)6XC6U`G7(ZhP4c6rGhw1NIJEWldi{c2) zE9Y~0A74%!vCkLb%#06p@=KM)q>7X1y_N3Ss7IZkr2+>dP8e3EzAh0}8)BvUw#A<+ zguxPt2lqsJCTBb(r1-ZR2sp};q|;p`tXP-W-)pKm*Rv`JWXY7#$WW`ACx#UwOy@P! zn@qzxqoBFLO&izN70kg()9LcHbJb)swAQq@GqqL`)Ohhu$r3)c+kW7Z2=yQ|jaw(_ zRFGK?VZP>^IpY9?%o5HmR222#rp}Z)aQt;d5{d5AR`LOIc&hA6f7P$-Uum-@aDYb9k!z@3f+@nvUs9~t z(@e!JuDz7ChFd8C#cS}G;=T@zMfUV$?6v;zc)LCcQbsP=cmn6Xh-pxten|U<9L+LJ zl$2#kwSG^2=5yA-A$jo)n`=EwsiP(QQ3atF2HBNwp=rZv1+xzNX^ZaI4+}W9oN=OML z@GRmhvYv2J;;Rl`nB6m*s{S>nP-EgbYmrBliVqXSw57Db;FBt$a<>X z%l5Y^q6IO8;d9u&52mYnK_w}HDR+U`B79ulTae^caZq1loh8?~iUX&uT7 z8>AA6z|$O(n>Uy9HBj)iQzW(T%u*Ga&ZwbKn3c}|(6H`O%HhA%e4t#Kbt%1_OG2J| ze7^1_xjyz|6JqR)2Y<@zwG9))f)2BN>COHiMl^2kyU;&{`?U z(B5i2!=*y1$#k5bm*XQOKNcY&7s7)m{E@rwbc`Z3XE;VKq zWppFy+Do3@U%pUycql=KySd7^s>;x-$0eu`K^}Sy9qo~kxh2fq22(y94L2)#KVK_B zSgIBVJAOeqI6&!4ljpR_r|88WyA#Fo1vn^>iC^#ong%C`kvA&<#5&Do|Ss@mhK@>uXx$W|q?S_^^t6wZc z?oWJnNTyO5qqKCKbyal)40KdgbqoY_63c7a5wmOd9S2GMIT+u6c$`D=+Gpfflo5w` z#2Y?mwIC%1Z^iUTbw=70N!7rPH4vPfC`c4yl=+4i4G({j8clp9xfM-~gvg(&Dodp= z_%Ugc6yD&q-24M3>|xCT^>6gqY;IhF>Jth)YhNW~F4%Azx#upU6CP_R&ej?yh-RoG zo7;Tyk={ZxlY9oWGijf!W`;8ST!)9NzVI)#eW%G8uxgg{92|Un930;dhtBCARjzoe zgSmSePsa_+`W5;k@837zw#=v*U5ZP@nro4i4TSN!!erl0 zpq}fRuXoHx{od{qO8&%c-GV z>Ccvl{wpL4>AZTL9hsVk^2A5eqr7XlnKKg#pALQSbLkaa)kR9CGPW<8kb}obwY0n! zc=bqUGdZnXG>Sgu?gnnxbcjJL-P11w1U&`u8OG4Yp|)|T%VWI6ow;W2Hx8awv)q5- zeQNO8jjk9-qH0VCPG?=Sdq?rx2Fo}%7XIGyb3>>BPP36|iU}hlCkrW@)(DHZP}2+t z)ci-SP;1!|nF~)P#wkTaq?4Pq4tY=+Zb#Nizb%O$Geb-lQr7a-GrJb97Kbu zr}m9IXY4x2U99Rj2iaNetOIj|=hGk=lM7Ix9z+T}U6(4r-y(fK?5i{=;asMLiNc+LOh*w1zHTjJ<^J z1`4FRDfBZIhaEj>+d?JOk<+3R3Nt`>dN!N+vrD6$wH=}cbnSz||(B_vTf!5jsI?JzT=kDRsB zCM;+I!M!Je5`#BQze-Juoy4oOa1y9*4!-iqK`J?jKLY6sGwsEHkI$vK~Z_KW2 ze}0)*Vm>@<$fVmy2wyk*u&5q6_xeur)ynMO1$ThB_dhA_{!%;tseb-b@h$qN;#<+h z(87vF#spZF(gryF2V>+fwKoS73xFmt`BVB$1Ar%>>6DyJY*g_9jKsf4yXk>5e}YN` zY|X4q@ZZ*){|h4mNM-Q>fCw|d#9#v?v-B+gN(HKtjs~oMn1bwX(1S=UD-i1Utjq#cVo&KpHs50qAT2 zv9cB`%Udd77vOOGGk<0ve*l;PvEhQT>KA=c`+XIx!tgL`Un-wS_ z1E9Rt0u~l!?eCjj{B7?oA7Jm#d;oR^aEz4!kbMIpU)DeJVST&eABF!TFF@O? z1xU94k%r~Zw17YwNDJIUEkHpGG{PV40+fh_11K&)PI>#BKM^I&41eNAfZ8*?oqMC8 z00@x($&Zy0C>J9@9s$Y=)P)gfAE2iKdq9T+j=b%?VN#ght^(xIZ>0ws6wrhNjrGRr z03ayw+BP)(o)jBhuB9Vj6nItQ`>a%SOpD+xQp zf07`-@lXDz5C1n=DF9>n7lKFpaAYq1N0D3az$Bh9)Orx1ybmE_(beCBNqO)9!UB;C ztm6QoA76p3OarsHgtNWQoJ#_ggOhcXt)|G+@ObxN`6gr2#M0x_c>s!;SGsmyEqbAW zNzU2EES&;&PvM?VT|;j3$b>JQ@Do?*s@f|u-3)u?Ndx)If!Gn58w#CYiqiU&kp}Pa z=uee_BI;gqI)lQq=ij;Q8lU2hui1mHEqvM_M%8+joT>zos2VYLCsvbuE8E=Euue?e zC9BT5ltgDEUdzZZt1nI5&v*h$KGK>L4@D?LOA3IK6HQ8k7eSnSi^VB|xX=`euP!GV ziYAD|7dC~8`J4p1AOj(IBZ!D`w@iXBeCqg-kc#xKl=7qZ?EI86ej}^B4jX=>tvcjf z8NtxT&p_I_EYz<|*wF;RHDF?~Oe79LEuc2M&6<-T&Y?9{P=dTZ?>2-}(hkCZ;Ca@@OUo4q&|hZ`~Le16cpwk4IH4q?Of> zd2dHkA=QoNA*8{F-hGh*hpB#_gxb*AhrAVDcMAK3iLhI~V>&`SOjKqmuQ$cnz@9=8 zZbma7Rfa*-s|I`*={M-mJ7qNp;Ikf%jnErl+VWzbU_}**y-iR2bqhdz`oy zBr&o@0CurhT4)KK>yd{x`z#eyc3GSuIClRH=EGV`c@4W>@ZrnKhT8@K{MeYW!j~}XonGXu-t}YLh_IN7b9j{T53#y6Ttnd5+dC$qSCVepr4dUSl`KAO23Hk% zmng$~v#jIWXJO+bnAty@#~yUfM+1IzG;ryKk2hQ~OZh(c~cxd~ovn3lYsd}ym{_Dgnh9C7||tZK*O zbD7s=ZkQ?@!+727!6w4y-p8pxl&(sZT)N5JHm}q{A3{2XJVa1ZWQ7`mb|bZXgDqHh z))i)+$Ny-RfUj_dp2iGTI5gvlDmJJ&4qBVxavOG5(+i&8yIqq0)j$rB;}}FS$9kg~ z+i52^yY1t6cca<4c}90e$>N4kE8V!svKsfelk7N2ERTc!HPXs@vPt$KcY}+5l1LJ9 zncS^cR(FbA1@2HzaO{4z?3K)?)!6f0q%*o|BV=lNjI;%$kmE%Pv|CP^1_D2+-=2e9 zDaRh57Dq&LxMb44;z75uy|2}!LLyH^@p4(mkgYMY=A5~m$`uEQyE{HIa}(w%xd)DP zcsx8ZTg*BFQG_j=TX7gg2hgv)Brqt^aj2b3A|iLJDIi{-76t`&_KRUqTm_suE52D* z2Hgs@z&6JZRc_`OdZ)xv#E9}|9OXw4GVyY7xJP;l&RSHS)CwOb@%o3H3W(eaMD#a* zO$3?nfyh7@#|KM6p#pb*#aL2a*H-?J2@aYZc(>Eh*mIwwpf{!-`N1S!LgFLWrhODi z2N~#&AglKbcvV1yKWb=kJXj3zeDhYKg+lNENPIs^6y0;lcb$;&;5LkQH3&w;g!}>~Bp9|7#VI6d z-i^MGakK=pWEP*e#$k_3g;*9wb@)=z`l_zIz>tN~1x^pAF%Ay}7ng(%^E6@^uQa@K zjXiA#${up>SaL!b926}N2i6E@SD6XJRXJ%(O9ZQc*Q=+;%w9F(P8}V>^Z&ezAODc6rPPVdyMrb3<5jE+cg0 zFIkM{#W3UdyE1YJ;`he8V0urISMWV{NB@27_Wb*=l0v8#7gbe8mR42^2Sn7_FGkm0 zF-DM-uA}MAC7b-|61c{Tvw67ISnh&0t84WSL(MT*wC!VlW*szaU%2v87rr`u{~|5A zJm43;L^IB3A|E$zXUXT90Y3XwLmli#wsN{OwM?5-F0vfoQkEEKpc?sfT!2yCW8jDy>yp zQwud%zMd71&AmU3W`;b&c93#w0yxkTC}?dJ?Cju|a#n2}>eW`95$^UM4vUXi zz$|T5V(#?#A7JfJtl1+5$yf)pF71g*Tub=A)*c=RfAcIuy%%GxW;qP)8yi+|@q1@3 zOb_$#PfkXT|M=+qD?0cyTKF^85&NH5$Ij5s*-pvYz>r4C#KuC<&e|Bb>VJhi3;@9G zPuJPsBcFdT^8OM3{11AL4G53^Zg2&hZvgD?zlK6=|L_aa`~}`)U<5pq9Dg|C{`&a; z6(jvY0ORF-Yl*j6B@h+-=c&X0CMy2XPX7`k{kPWm=fvaxL8t^w$pK^Ln+=qWnI3Si z0>)8b#{L$RFawTKdLZQdpD2kPunz*$`G3S9tW4~HXA?*W%*Wp*^>3WRKkce-VGdwjwJ#J%AVlY?%K( z!3QXhf8BupHZuZ1i+`UO>BOv9ex?5ycy@{G)YPhusH0Yo-(}dmF5*{o0%p#*$R9z_ z2ea1ZK&Z9P4~oZR#-6sJ@~n(?^0~cPf^|Q8PLw09uvg+MJfw|!?3c;OGvRh@tzq|% zY)8iqr80a~Iq%l-vbnq(x;Ban;PKG?oS@TL!LZwkN$qRY9|MRm5xyjlkRNv!B+a(f z6M>Q~RHVm6-CFj1b392cpg)T4NZ=D>^py z4en>j1HDZjAy@nz=MjHFoS9X}ttc-&g6Crdul)Nr8;HgJf6j^jTZjI?o*KV>>c38n z|GdM0n;J6#;rTy{_K&GC&{=@rOa5C2U}5H<|9cN;WrMQE?ssVQ^1@psdiMn%zUS1y z0TMCz7M@RH{Ty2taSdb}hj(?u{yjg)d-tz*AqeE5Ym<4n;;1B4h$@Ns@#KqMIYa`9 z%8A&`id7=LuA7drij*N+%E$^Ko8Bl|gls%DUijh^k?G3SH8o6AE5K`Ng$V}sUEW48 zSaw2u#Snm*OhaeiZiAPZ@BdN4gWwi2QS5Te%xxFoOM$Hb?UF9NP*tUcQbuW=EMg zpyIDc{?q+rLXUd}G~d2a33?81k8G^)@YVRB;Y&c!@5+b>B5@d@*b%UVe)9zJh&POvUxW=FgaN--DYADFTOTk+)&z2Q83r!b zx2Hi_N3I0TgEoqKmHhrs)a z4;n`g=k!rzja{0i8M6&-1Ll4C)9Bvtiq$Kh*WpX*?|}*mzY^5Gz@0*M+kTklWFP-T zwaVpbl*0!LkLhTTkVd)WJjbO8(=6|=*G7}8W6HKf-ZVFMId(05%8n=J1Qrs36$bZ= ziD>KZ6DvAB++oYN+>tZHWQf0VznAN}H9)wD>Xx{JZut&5kNre{yKmBVKYM0_j3w%fRa7@9{1>e zvg`E0N6Tw+?$6kn0cuxJ(FS*sr{5I2c}u*YwzgJY(d_PM@TQNRUM<@`V7-c6>iX-r zMOYDtFL~}TT4=Dv``C_Q$@l2AG0Dy4Ojf?a8+}WidrsS>L2*-B2$4ZN#SvvQ>wBRJ z&$`AK2p+HH4P$=IyLS05qC4?`z576b-1cK$XV8H~{X$W?=$l@!tKJH?O^3->u0{dyO%V{r()diTx z>^moJ&gjsQ%pm$>er@C*;;l~`U-NlQuck-L3fUL0{<035Pt7Y~P3vgGtOUik;1I%3FQMz~ zM@y%i8s(Gn4kDk%!r)oC`X&(yG@rt!oo*C(-{qOXU~OnTXR0UKTR7iR?G3SEyK^EF zXg=+h9t0FB?=0nw=lrmzF~q>N4FyTk`_3*A-z@nJpFk;ZzYX-YJ>|Pq!oV|%pPg=8 z!}lkt$i7RWfoGKRcVpgxX9IfMOE_$ItifFkizusMs+RMM-E(#;opZD9_r*gDZPn7X z5z71LTdpVTItL#0F~3_J+eS=xq{6)JVhumj)l+x_{{=pyULHbA{Ln;{`jy$2sNx*F zDZPcLJ+xtO>HbMO5N+7zsZu`a2xp9lC*z8B9@8-)`|5XXC(BM^XRSX;^syx7baP{8 z-a9n$9a;NK`=81rCiD{t^ybbO5Xn09-x0+>po97u-}Q6|>cb<%+4+mzy=QMp_^wkR zkRFD^3_j0K>GkFL>x{v&7q_QrvWGCPy(ux6PL`qv6IB%G zys}wH6uICm&~!|s2lMEetmaQt=)3Tzx`xy54ZHE)D;yHzeH*-JYi*% zrc_i;$KG*i9sBPOSun`q!(HXVkd|PKdr$^{YrFf%=sh<^?R`ELThX%SvNL5h zR}x&@#`AnlF(-d9)_Pr&)W-QSSx_dRh$5??N&dqTkA$}-Ci3@)32GI->9YFi*6JTB zDn*zKY2=u&ZmbENsxt^|>Ta7SgQvrOz8gCwE91|@FukzN^lbKnYqy&xdOJAxyY9J# z8{+gtS2y=g;iti38Julflg2WXnVfDpmFE$lWRLli6T6|KOL%=kx=r3(0bbuyziQ4$ zax@LZnT!r;F6l&1FPf55v7ljIUOrnzxI{}{S=-MgmY29Ruk^&5>_m7J*{IYJz2rj> z)9N!}GC6=ihoXayg+fLp7}W1XJi=8I8^Rn;<*C|hhq_nGEuso8pbbj_fpq?VK&!!&pI zestaeRhnSbP0ncRvvIne`8_!3(ZH0!&f~JsI;*rFfo#WFW%MLzW8-7EX+cofN`IGd zSga{Jp05V8kQlGrN|J9;?zbx`qA;lD#HQYmV73@rQyq~QP3m1LW@=iHu4N<&lD zXf{5(TwQCqY}6uA74V?9A=CTiB4vOfh@3cUA??hlQi$qv!ORo#aV^tdXqea zoa`$O*AEQAj#wANSyyy6+;`98M7Lt7lI`&HiuJ2;8eUI9S|@gB&D|Cej7KsWSVK$A zd>_SLZw|}sDrjWrTs0JptV)f|_88F8w!D=+EmZ}Xr;NXqkB`->tCE!@a0hO#P&pp> z7f|h5ud5q5b~Q!evZi3AERBy}BH{VgrX^H5j3QfAyk0%_u3E6Ix@6Qx#IZ0PRLrln{I9PYhymC&Ol&wM$6_;xr zM8$b)x7?YZBz*SsC%zZmr2mJww~CIVOVDfu7Be$5Gcz+Y+hS&BW@gD^M$2MmX31h^ zW(Er;U4K_sRd-F#+=shnEj?tWA|ozj_08(U~Ru# z&`VdX621M|5nB`l$AKK4eSJ)KTjx&82}8i+ zwg2q7H4N27l%&v$^zNscx(FM+ub~9Sr1lKAonf(XBjVdj14e~R1nL%}>OBc9e>Q4;=Wk%^vTMH35)-QtdhwLU zvumytpRbWfZkSh3vThW^)KF;Gh1iqwMbu-RYE{(li-i%nWF6d>@3m>DS4_e)NY>(l%{X9c2z{fT#lepbzhKnli0XUdAa z87Sup&QM)0knU6}WG5ODX0!tu_m@pL4N~vRz9Bv4Bad528X&K4!T54dE!-8--Sdp& z%@-kMTE(=#qR=}&VK{MKORpmTj(tF^jJ&)H4a-JhT#1>_>#`H%rq0N@-<|ViB-b~g zAcA*LY!h?s0`;oJC6~Z$8po(DTgcgld48o6q^>{Ez+yE^GJPcC`FJcNmq*P0l?=}^iFT~S{Vz zXgouzEU0K>3n4r`-;)+M0uXOg`3xlw2gt3WgUF-PXu;F=V}zPfmKH6e#@@e%RAG}ZOB<^x5nHq}rL2qC34YhZ_j|`M_4Jo48d4$;2i^EDQJNHq z2-)l6mWqo;W=y!mqoab}2*BBph;Mtx<0C)WC5}r) zsui5~6?v{-OnyoyQjWq_nA?dN8ehf3#lrvE=D9)@L0T6jR)7>cURPI5(+;z*_Y~6s zlG5a9B*TWUALM)qCc78+KrNKBGaxCJ60Q!`vO-0nKZ3l1+^r?l3|0@Yb%Er|z|0Sd z1{skgV_kzHnkuak$#EGHk9p)le4XM_GekLv%zXHLu*n&b(6zjBl)U1?7ET&v_R8dE z?_3yubN>j>S_9EJCT$AC%(K1J6gG85s8W3cr&3B3T%zYlySn>IUh!HX40kVAP_sh+ z{k&zCIgGo^oJ4C1kD`a4x_W9>$z=iTD}fjkb|^h*862lq@DC|Ic(6qNn*u~+sYZI5 zt9!#(*vKMq5YdhZXkr#hD=8D%2@}E*2IdfQDIb*4HKDYh*ja)GBnuha^JFJi>>Z|j z^V0QMa$e!n@oG5>1qBU(cy~5>ZpvlRkj`D}68%a~zXt zEy>teO1@8&AtVUpR47J06#ER_k&SsKhJCJ+Pf&Tz-@xtwWZne#R;cuF@bt;w3Cu5k zpoqy<3Z++yMv~9OP4NLP?2&Jq)n|ux^!_^(i$y!tcdA%plCYDds9)tc^X0Z06qSyb zs&}Fk5f)O!<#g;93)B-8|+@h#I%yX zf1IvHgg4}|UummRd#9m=oK`DWiVJq4oLA&pncFm-WU`49km#%$c#Wl?YJ;_^7)H_XF@g$^nqJwY zWJ7lXQlEC)1`spjWM7KBfNz=B5!TQ48HUm?L&#NrHjR(c&;eWS#Lv)DuqXY7;rc!? z04#0q7w4le&UMdSJI07AD(t|<8nwKMsQ~3P^}OD);W`(;c(@6QcTFHdSv&tUB1O!y ze2T}eTs`2dihXm#Rq8s+C+_+rGSDDefhK`>+w_k<~**GeclogDTbD|i2 zNc4)66yjFy>&P=C05nU?sPi@Im4(#u6U!ak-t`mR$_D6@CN-sd8mg>3a!>^S%%2o8 zl`T9mnDuQoUP0x~k~5+28kK{SmAWo)4l|1yZmRI+gB}o20MZa90HE|AN=bmFm6@LLkD>8jQl!5ou>WO7`%j4SeN<&2>J&w z14K_8EP#;(kavHVTLPjZ0Kvw_#RyP+G6S$Z098g0(5V76p?@-#8{u^y2z+U=~v++MA3e21w{~RdjB}`iOGr|sE z`9cHA@kS+*N&*lmH^8~hgG_vYhSj;5j7|ceDh%Ah(jE!6;=wHH2x{drRlU1j_fkE#!v*G=-LF$zwfW62= zZuP6w)jE%nk>u;D@71Tl)+e7SE6Y=k(k%4)}X=>s027nV6x9!4&YTWe*2urJ35&r^ljm z&pXy@qZ|!i(rm!{0J%OF}5{X=H(hK!HgRozji_6;B7Ae1Ol%g}aFw#?_N1H~W zCTYL>F^P)K(a_>$-!BEEF*;F7^778XIqQ$ZA{c*yFW$a9Kz;fP{2=cnGYVD)EA>Ri zF|#De(C%|>Q$NV3-*Quh4gA1}E}~O%;40Y^(sO`b$pjgyZ>y|F^|js5h&Q3ewu)@I|A^+p^etEz&=ff@xq1MYjaw(7MVz~jlA%?TwANeNx7C#G5hFqkeIxZD0UBfpSLPKsZV|hUZie zvV!A|ycAae-dAftej%jS$a_D=F-P&lz35k&f%nG+eI}TZZvdVS`c3Z73+p}gYnG=M zJuF~&ucsejFGsYLs#LDWU`IUX3FHu|$3{3VVBW6q;+X!P(B2@ZsbGWf6XzJJ0qa88 zYnEu$7kl4EP-h0;8PVAG3w2;S!ZRL*{#TA0PgE3YV|>%N%2p0I4O0JblbHUcxcsBTDU{)HQv*`(tLz0E! z?3J-3E4L!7ko9jw#yy6dg{sl$KfGV}6X0%1ff>ag5+Ho$65jAK`ltKp-88=0_GgKBV`KQ!G=}VG3g@~K_>=ccBvV0 zQ&8EU(fdCfSivMbMBWDS-TgxYwuC@Ngc#QZ%4}d??lV_S3D4*&Y|zn!t0--3QQ;64 zb$>L0jVyXd;|yT;T1n&d^E%D5BV+)|kcNTevR35-oAhlcL|}x_U4*hcW~hJaBk=MN zon;b&wiBYO|LQ_%6s!cD1lOc+KVvuN0JY(1(9YWcuf`vFU6|1Un-A9D!2V2eh|0eL zlZCjKB0UJ?s5|lKWJ8+-lTWeFeRZ?f{qoM(pKpxwi5JWm@hV?85wL@MM$y6*Yam?IM;#S-NDDi764iDaTC zVl_jsecKw;Mq81lQ4}FMUGTv&zYaCR2rpl5j(ySExrU|+bqPImAg52o(Cy=U0fzwT zk5K`#AM}tBdLBd$YPOv%%p9by^4w%U(ovDqj)Dhb&)IAFcu1Q}yapG~4QovdC9QLw zOO{z{m94WB89d2@2STf)9kdpyYWZnM=_ki9(xMR}TrwUEZA>Md&6*UgVUk%fwexl3nJg$!URf9K>QBbAqdk5EbK*i9~q z=j19^rnT>gH(-z-t45_TT^p>$N--3#;&PB`^XM=KMOwUOijes=B#U|=sw6818-ZRc zm+W7eo{ZQ`N=76l8;cMjl|;EnhB|~cA462_{wq>03kjnlCI?dug=GicrB8g&t1_f z)^EyC66iZa9hL&d6x%`YHr}xz@om=y!Loz98?)dEYVGAioCG`%YI1;iG32UVzdAq)lP_0R@P|kfj<77_bwC|U}jWq;lHu$wx8}8?lg|EP!C~rf?=>pvgX+M zC-V-s8l+YiR>K;*UnLauUdx<5s_zy(&fXxVhd%E;wg69Fww}F3-LyIKywslVJ`;{= zb^twID_gu_&LW=r1hMgj=hs09>)l>UQ+R7v%T0}T=l=IXhzvMWM+AlzV zf0muE;Sii-dIiI6KAnj42?ao#>4ZhH1G_JzC z>(F;V8~Bp($!K*u>apz`x$Vl9c^X|^C7Ai|#u4*!Tgt#xhnA4y)*dBn#(eRXOxq-g zpQ{xvFU6tiPrP~J(F+O!Q^3g-c8aTTzU5MhZ-HvIRgN#TzUzeF7e0tFG-16a_@k^7 z)-MBQib;j(bo?=bOrOTxr~7;%HutJr0w}v1N>^P%du5eCt&Qh^jm(Mh&3}#k)6i2VX66#K`;&_Bqgh3TPjs!?&O2S&Ia| z|Il~4H9QdFdeJw#pI4BJPMNA1ge<@YFCgGB*cz22zErm3@D~@_CL&ae{pK zd1BYsG(fKle-TD+yK~pKqh}K+U#_jHQ8V!8BTp8+@Y>hV^swU`7XBpBwEaY#iMnL)ZILXoOjptC2)Ch&FzC@y64It%DvDl0mhL>X*<(a?qf1uFkcbQ!ph zA#fg6GL?v$V9}5;#ljZf(8fojsYtUttgjrs$4ldM_3z*wkC3M1ndM)<1kR4szjMmh zde-XFk~u$T?u&;DN~BayuAe>v*>#INK680AwdRV{6B!^g%9ghf(EaRRA0q;%xlnV7 zFB-s1oaT@?b+czzND8|@(j>g{p_a~J8RoMTt=+%l;gy3ER5~vih>rlGP32Q&(Zni& zNp%|AU#L!vF8|^355ZS>q-E*&-RF|r(g72pBL61i?cuZtog#FcUizMlmLI)I=25$ME$&Wpr>-P>5ckJ~t%1UUJfw%vcev8pkgM zGLAU0r!={Bn$@KxYysbSd+)A!W7&q_BsIPgxGJ@HATqZ;VZ=%6ZG7@WzS$>uU~g-c z@`W^sSB#v4+nv4@`;C{iiQPle0a@}KI1d&mu@cD;3p17VIg|uG)S(GU>O<`jM^jj8 zaDgzhHu2=SSzQXwwmcs=FP;y*e*b8Ps?k%@KA}#hc%l^AQh=A2pCVd}G+@FPt_r9+ z2Ym%(VO1efy_SDA0rVn%?m)scSK;x6smz=X*zl{}9ZZw*t^c%T`?&=$1+qtCX-F&` zuuHiMB!^S2wF4-WQE}6kexXKl(q?i3(Lz5eV;sAfd$E_IQT{S*nfz_XrjkLppR|(X z<)Q^Y9tBBJNRktCa4@NYxoL=kZ|;rmY8svx7Bp)_j!VaXAX-sr%vdLk8?eb16AU7Ln;bvghZT3e*Vi4QUlPMXF@v*HIcuz3)r)Zk16YZ>0us_;<} zZ5T3D?oXxbk$q-_VDQR3arTjY$My(!Iy6yn#YO%QCZ;sOcmp$NwZvPdcm=BLKJ%YB zzO1Tdfhr{fTjuTyo_Q&jTv49!=|uJG)uzRnZgi%8?Q5G=(a7`A^g?1GV;c?em+Ys6 zIL1-nrG>i}?KhRHv-8y&yo0$9pT33)j(6@zl7*3y z_c*cI$e=M)w-hGmBj#fH8aXUQ{qbXCguZ1Sk@ zeN%U9^bXmW^qG2~AwiiJd8EU4umrrT4f~+wW>sOjRA* z38j6p3ae;Xo;D)AJAA!wgYK9SaZ<*Lyr<{tQrMp1{scdE3ExiY(!*Z~*>4MjJ_cW> zDiy44RVX$Efs+Ft7q&?Bik$@HRW4@i25SdYC)GDM!+KyMslLVX6>hughsEKjt$Ph7 zq)+m`x-=SS6tC_AzZA0=$QXrKZ&aus1zSyI5*4hvLvM+40@iu(zUp!Kqxg`<&R?9O#Uo zhx2oo9(e0Cxbb`VD)w4a9?laEUpbB4!u+Ac?~SEPW*#3W{QcP=U&xjZ8+Qnb7Tb7H z_sYqZwM;7%JcJ86y1k0`g%oosGP5+lQ|$dRweiNZ`R-|?5<07>a^K5P%(fwbV09%ZbRM8tYW z_q!X^=St8E-q;7t_37tQogH3ttj=)t-r|kG@1E7|RKF(ppPOPI^)4+g>)f`0e|{1m ze6S@C$&3{rB%kG0&sFbrA7c7ykX|KzaxBiyzEN_#E-hJq@q@1&xrQBguSK71FjL41 zF_{qm^6|ug1><))&fH^k8v{}dD^d&JDO7_IorQtbIbu6Kni2nUQ!Pwp|{0+PU^#KZ|xBW?1l z81_)$eq1m4{e7rbr6h_Va;6>t?oN-M<5)q*$3RcdK_Ot5I7vS4F+(iJg=YTi?#{_+ zm$SsuZlihH7TeR~{meziPuK5bq_5P~w!QEwe^(A?jJ?UU+Tr8zy5=*+%C61LBAR^4 zPhpEjf32mt&hc`+C6DXqD&z_ixT$K zI3)^&<)Qdt-?`anf@2XGg2*&7mj$>AVUT?KndggJ0Q=!c7y|@GEpAxjtd>zA!md{D z)sB|gvPS6Gb>xi=NxD*P51J)%uAFcs4o*wU*9ri=r@m{%5RPH^rHHtnuGn? znpirFruFIf_Tv4HCT`!rv~PlF zCB)&qj1hAOO=;!yI?GIt-nz!)!1>!>*M^&LxgQwkXV%n?FY9Cu!_U?^wj1f&6z--%6mS?e9J4o~WRSav*y#`;8=tMws$VqS}rvlyEWa zBf3x{5P`#8ko3b09dYA1FOHK8_#Stpi{AoXj-$5ghhLoHz!=_dGPbH)AL+@vFMvPJ z5wAJ&M8lM3P8g>d_4yX{5nQjS!o$@VT+NTgT(!5c$oa0}ReE#1=$7AuzTzu$-qeS3 zS!ktl_}8U(C!mNDiyG0dJH2S-emSktH;MJ>TgN!wp9Yg>j#A@aR=C@IrUf@c0gr#) zV6?oZ9`y(bl>x`b56K0^MyFCnrItD0Rx>5@yVPFCp%&PooEa)4l?d*|jN-Cs$+;Q& z-r$S~RqT%3q8a*3R37NWq?DKpTgC>~%Z*owwmArMLj)`kTFJl97R|0X)yeAmSzDL? z3Ff$H@8+j9WQvB@YPH^YQbFeGKVXh}#mFeG*8mKgKL5$9-!4xA{jl1P1Pj+}|5I*(z0Uh1+ap9F-A5k%CZpk}^ zy$V4R3#+qiz03tug-hPn<0bSC4?#7F9(w&{|f z28~Tgp>?&H&)6I-`;_b^l_M|h7mB5Fat06O3K~<3@TfXTY4%hwr4-Uepy@UZi%MLlgf)N)D7zsbE$M7YeEEu^j` zjb~ozuAbFQPjwhiPB_ZDuCp_+m@RxOuz+^YM>{VRnuX5}HQ}lgr@~hx;X_~!^|+k- z%ErN<+3)i?Dzg=n z7=BFPFf@=8b0Bw5YA4mWnq3Qe*keqadp=|50!yH4m?xDF5=jxJ>ualPGVEE*dH65Q z>d@q4b7Mw#u1*eoi?Ur;<#3@rU2KSW!4u|SycTfpZ*+=_f^}aE;T|BMU`rl{KRvxW zT!rr*UrR_Di6&rwTyw@HtI@t8O?9nxch_3Z73%&DNdvjr%4#C)y#KgVDRs%D+K5@X zO?rImQR$e`>MrxXvSmJNnn82*w>0M_$Y1bR4*h0@u7Xxz3lGsK$x^-bW^gP;jP>r&l$0FrR6Mg+^>U8T$~8}TxxZ} ze?P!BG=m|YhpH#R9Yc@B_}U%ZS(O7$SPLE@q^x?-5s2f2%4)a??d$roVzx2tMcuSq zTB>fQk+R45n2-R`UC8h8twneqDp9m_6 zA`UNVXka)^)T(2JQaFQSL``~rT(Ke?yquI0#(L@MWu7fn6OQg{9i1K=GSKjly?W!% z5zA}Qqy0E{IKS(&N}Z*+Ts37!9f`9jFO@@ij-9S3)03yqL>8h+Q<~||&DP%S%3cIp zfm+V)`qLAwNw9W>Cbs5A*2YFKqu7nLwrN}y7K>1;s)y%HIE7X=Zr^t6J6ZmBWRigc ztqJrwk+2-Uy9n#2`!^~FBMekkTEnxYhHCu_y^Xrifp@5p$40~mqwv9DLOaod&GDPr zS?)>6FRLkm1*6%$@luH?aw~C(==^0nefx`D9zJHNditA*`LX zeuPmTj%(~s&7PoFfvBNx-_d=uy1^gg{AKs{_{%h7eJipytyqR<~#u(yVtbzLI86N68Btst1vh2IDZ=9EH7 z_^G%i$HXLboDrwdW89*F#8{MSsx}%{#fRch2~1+8jf|UGZz7R)o$l^lIx%v(*O$GQ zF48W35G%M%9o(h*i70hAlKVoh48~3F_yPo@q9Gi);ctP$Q=El(iz})kQG*v ztYhc2M2}y~Z;l~TML^{KV&BO@tz0U?q5Vl7QieVuG2ICU?&r*s@V&{9V#)W!EV9<1 z3)4N#X%pd5gQX3FRFoL%JRAAP&CpHgSv$Y-*LqAg-@@ngGb_T8UC*Nz{pJ1l%gx9x znmD`<_u-kEl1Fy-NBN9jYtC2n9FM%P0_q~x)g`Pw_C2clv_jAPogG~dK2uoX7F4Qs51 zI-Y(}=+&H;UAh$aB)XZ*7$R=?IOH^X$X>oK4q8F~Zh74H>P4Uj7g=LDiXFc;{!$9V z0_nZ!L(l7*I!E~opxAiyHb;MBQa#{ZkpDI#tYhWUQwr4ZhZFEA3pA$*S{Ai zNMg*0QB3(8!W>*V{XSn-{JTAoM!2DwS`wZ5DnOL*3CP{G=&7Zx6j9WnKr-J*U_Q!x zV481vl+)K+3!R<(-cw?Kpy}L7QgO1ju4_A)65J*Ed#$N2olMHMwK-7%bKPI)kr0xE zLY*vB!)&A0yBX z_Ki&gZWFZANCk}D+nPCew8g%&jau=k@uwCo<&>r7a)3nt6r~%u4x){cHf+LxT!&0< zGAe`lOABfJ&R0fE?+XNozDcHzR2T@6tbu2y{&Y?C@j-Gi+&9i=YEw-eGq=K(_2qhM zzHp%$H<&9;GZvmRbaLk6fV-wkTb?AClGAK{-zK+VTo8Y(l1t^Fx3_IFEo8w6@ zb80FPEY<<5VNCDdEG+~$phDJ=n;~ux0zrBY3){Kr-sD{FJeyeV0@v8*4Sf4g$3LZ~ zKsQH814H(U7~#o8#oc2hG0}SEO`0P0%3;FX=D@%(3!g}k3&Di!V*EG|8)EhRs`yH+ zWvfVB4v{6Sxn*tRHndWhEY@gE`x}?1`cuGnh-)UxzyE^&Rlj0*n0}4Iiq88Lm34H~ zXlNkVCVtkFmE|ZXE8$AvI5FQh$WerxYAZK-mAmQkRb0+PuYc}n%x!{pm9Ny+!AHkU z!s{SwG$f}l6YB|DU}c@Vn@$L0gUzphgX&iT>+kgpl_=NLqa*pLpDt#zYNeUgriLLh zh$c$#*+O>n_7kCuipQXqdm~C)h*}xTN0_k@S(x4PAxXh9A`mxe$^gi5|9NEh2LUL< zErZ5H_9=&oY%<2_;WdZKM7D@o_qcl5mMjw`a#0su2Z-I(Y<%svz}~z$2h4gLVpc+) zC(r{K>^8SlqXXUt9qc+jq6$X4Cm)U$;47aW49`BP3m60ZBNkIR8xyEtn=N1MvdwK# zw_rj>E@dP!M5jWfiBee0l3(IK{9u8OE^)zXFu}Gsq4hoRerR!lC}=##JG@zuJQo`T z)u`Sqd`;1GEzT2aCSi5PCS1u^$S|@d5L~Q?m(26{eu2&^8vtEyI+m{a(ZNjp%!M^5 zYZ7}SpeU{+NjlRpDNqv)?fY?M?u{Vv-=PT>fG_FakV5|+nUeb7AX9&brT!rp`@gG` z2Grs*vatPE=h**`b`U+p2iL`S?@K45$PK zypM^4>reYK3(J30NOJ(Xp1GJ9nE>b&pjMiRkquxB`*REqHYR#jc0eCAV364UT2=qI z*8!p}z;gaSHZ(v*H9ZTU<`+;a4B*@Tj#L38V1EHqfUW;5^=Ie!Q)CTTRQ*@`FJR3- zpn@1+9|PFX09DECe^%=Q1YRukfNEe)z_R|o^)LSu@Wl#P;t!B`0S*Vyt^ul}0lm`z zo(oXt{I}h*0KVE^{mg(;X+Xs@p!FJXP&RsIHWq-xj+p~cL(KT+9e*3N|9r;vXWKu5 zf`8hUoe4mT{kdlU={NvZ>_5w~|4?Cn9f^etP=Ec0NBgIDv#|j7__u-OWMTs}LH`+I zHa5opXme%*d?LV}nF0PZE&wwI5F{|t1D*js6`*AqV3PPFNBDQff`3wG{7)zm3(LQ( zSO0q}@^2b%YXqdP`1#1f(q+UB$0Eh=K!yzE#fG#Z@NYgBdWq7F!L#R6=dqo!nUAe( zo;$Pc-;E()2pW{&4^K`>uUT{(zC~aULx zz*}fxe0s7x>ssKyhzqzOWPbAv^{$}!j&7l2FBm%%fRqfPlE&end1AdQx zE{uP<+x?S+;qMF@Gb_Ls_&+mf0EZvfzc6Uuq1;u~nyx;*ax<9|hy}rwi{vSl1;Igp zjagO2XTrZgrWYt76C%eGA&ZMqwuuoHEVC9A&`s@177JEw1JPO1jO7teAqwi8qYGWR z^syg(OqYY~h;Q9_Z9UoMTvfW%`Fd%4T{S+fm@QBt69PFC${zD|v=piGQrbd@l3C&P zk4waeQHa*_CLb*C7!9L|cYoEJy!E1e!e?XWxO%}5zy=L+F85Y4t%^EbFea3ads9TO&L4Q z^eva&=_bXE&swYZ<4zM_lgDMTz(>OKIRJQ=YdCy0^R#>t&tS)0`ROW9Tb=YirYrEh zxo*AMb+JQi_9`3*vDqNk?l6mL;kB)0*->ybN3FAL%8`0X!pq0o#r2^ov#5ffpq?(( zWzfax^vEJZbNY?Dv(wgAPH(6;fyaM}cDMt`=c*t^1_O*;vLS4S_3)udGg$QYmsq$9 zuGF#3F1vS3{xeOLbv+5j-pK+AE}&-?`Lx7k>F5LOo1c~Mcrj(&-u>a+(sE4Z9jq

1-&M)%DEEyv0S?92ghVhmI9RKnds&wmVDTrrUnTjE-D~=G5}A z3)b89!L>K{Bo$6_v_^LZs=z&tPQ_$d*Qe%_ET2!O_ej&+cfU;2FuARWvU+{!FF(ij zmE}odLplZq2$I?uqCPT_DUmi?Jw5?^n<>-?2LxpF4h3-bDu|Vwl3n7OoNCGXH8EK{!YgUE)EO#9CdOtBLwPK`jSFy z7A9o0jwLqQrFv8Nd!CtGw{ir}dY>XC7Kpo_m@W;<4oIf8zSuTqNz{&I0&yjVu+=5s z`0o~~_`2`wHwqgj_F#S7%kT3RG20)k#b_r@^v&4LMDQP(53{gx?6}R!OmuwQA3crO zU`EOtq9sL%Z*>N~ZN{Qnn%$8|GPS;pLR_$%I`G+K{J>jUdrDO&C{Q?&S?CWUfC^;>dloYD#ZdH#ioX5@t= z>gei`W+>X&+IeH(O$$x#y(9kvN0dy%OL};JiNH4?`fsRZDE!sR?P5BWeBog;MI9CI z!IN!2(_)$g;Ba{b}@MqpuWf~l}9fvaDmg!%kc9=JKm2k>0gouwc2b8Y%%|_Vu zg?Qr-M5|tsAM;$HsYXl(bcf!TXW{)Y7W5b>r+yVWk~Fr;392B^ojr(bRNOmLC1#eQIt4UOkG#&$?# zj8DH=lT^r?=<0^>aJ~{HkTs2Jwkmk^+_#)a$fTv3R^wfT1v@o?)d13 zIjE+KMVk-Hpq&_IwWKa7JJh!rSR=rz~${&v4eGZ-OACM~W^EWXzWrPeor?`%_QO<9^7LsBuD<4+p$nPREouw%7j7O?f5 z^&UgEdM|`);`n&)QOFR$vlJR1OMRdVNUdH)>ar`jmgU;sUz;`e1y{Rsa!1+jNJ~-UDR8Mtw?19v=PY7-@wZ<(wuWSPIvW$)=O1oUsbWM28w|yxW>t;87__w2~&0oS>!$ImN@_ zIxfFvG2VQUJ7V$0DqPmLZd4s1EXi1d636TM?A&AqM*+;Il(NIsH#UxZ<84vW=|ke! zQK{-BuPr#x?m{WP8Ra6SXG$CMIaH>TU>|up&}{v9`#rufg*f_5QB$fpa@1}TbfOnM z$Y|5}2(RFVAim7QgoUw(^5a%!icxqaqKhtVOuSmPe9sfXsNpgE5Oua}!PF2nr(Z;! zrJ`D^W7ziweaVZ?av#$sM|qAp`}fb#_U#?pNwYUClb0(4Fk<9{a8XiXq!$6iIUY#& z$RRaU_s|0aPUn$rAyA0>t0j&ag{yUat`)5eJ}+lCGiN6kIgM-!J>2qamBb`u9+&g@ znN9pHdU|$p?lIHGbh${>5qgId^>Sagl&yW&7|eviG`)qYGXBznv6{MydvBa>ZIkr! zBOOn3j|0E@`(Ro=GrABxyvTg670U>jhbt=Aav`Q~&SZBS80{SupTP+P7W;N=w+<7f zrS(DDiMl#fuVN|My9|1YYl_Nh6Fb;O1yQ+V$FN1L~MCj=j)vt;9S&=EvArV-QRY3}t^W=UOw}hjrr9U_U&cg5Drm65~bm zb%~T#pw`o!6U1W!ontbF?Dg#2+_Ibc**+g?*MqXneKYvN zwy=XO6Z(#Gto5Fi#b;?Pko06`^1{OMKw?O@%U2xtBHrpJ{}`C(+v&92YjA%ah__Wszq{&}j%$v^39G!_O|}KsTIbkcQpbWj z$@zYB>-5LK8VXPHJp5zSz^9m#Tv?}eRi*WPuykWlK6OX43EEML@CE2}Q;5?CyjY5K z1?-V=g<-?3qCBrv6$Z7@dkO4^X0!+&n|b4nDmI%i-NRa?c<^zIyuxQpcHk;iXIjGB zI^B7RpZ(&?0nt&)v=c*D_2RnY_OfW_WN{+j#YDuM-Z}eVLMsIFj zlT+T0P1Wu&9rYrd>$i}KzQH?Km3|KfF@bEZm10a)vH(vg$%xqoMH zk8-P&O&QIf6bz>qM2Ln?eb#ZOfM3umW#RbAeU;!lFvr;-+WJA(s zQ74%`k|dA&0&F7%^-*pIdpCtNtH@FnkYA^9M{-BBg|;PFGM(RNa?P8}5G+ z-eqA z*Q6-BVr*lcqYPs9`)PivijAY6|J;{@F8}r!t$50(v2KzGo-T3bXacywCvO#p(~!tu&`~Gj_>lpt*)%!XI%TxxuwQnG7J8R zZ9HRcC#Th)3-7X=B^~gPF#_;V32g#N$YYviqm2h5x=p$a?8cGE&Eh})WF7N;cOa53 z*YpA=WG|P&71XC?OYPdP`6#@Ng0FmcDv`R1`aH))yC_0x^q<%J(ulk_f)&s}d4)3YTgqw@k}*e9LEiI?JiqZ5Ds;#m z#g^Pnl(LYY6Fw=i74GLJtg;zrC6<)xH5ws&wPq*BKTOBlN zhzdI21-jX1JS9DqKb5KwZ=7kP!80&o%$X>LFO)B!|8v2>y2&e(9R};ITXUx3xOyh* zC$_tve3pHupX1XzhxbbL39cI=+MOFHW2KXW7c?X`KtNtyTZIr?Z@Sv;`lN?f0#mw0 zZw1$LC$WVqwE_`vb^ss51AeNX3Y6b2cEr=Y+zN^h&u^;V$)?+u@XdNYsgj2uPsBTVW znyHy9jO&LzJe=0U(r;_0#}XK=d~5RTixWn2mtri?SR6#u#vJQ2Ppe{=TJ42besAWTJ|Ks%Cd}bf_jD*(!?cS`U6ai)g5`-a1U}&>;f(uqJ3B9|2k>LY3@80)&+l zc66U2CWVO2?pkMQG%r+oN?53w`i;bDpZYUjh*oP_xYQK9s#;dg#2j}6zm4`I->xB0 z(|v%chGaa{ljf#f?HU-cH~sq5#HbNam^RnzF!|^{Ho(msSlU&Ua!7iXR_I8PlkR$M zGmh4;*~jqeG!bIj{24^uRe+~3p%I!LLom^Xr>dhpVkWWHo~ezZqY6!v#*M_)+F3*O!Qqw zBc-w^RQsUYLdd3Wi$hBdr_{ZFYiM09sdWC-!X;+wtb*!M!2zu@MuhxB_)fIbBfPQ& z2}OpQ8i&HuG7?mTS43KOVQ`bKfBZKbr`-=Vv!hdu5162ov!I8$BW(U{j7*5*$^M5{ z@M9;qnIr5=EZYgNz34Oz_Ep8>1D>t~+XSXr-Ws*?lg&O+FTE}Y_pl-g2Dy!?<`lW` z9gQ4KU!T~*Q#md$E-+7&T&m9U?2k$A_2dp`NzYT)TB>zpGZ!xnqPoM$n%ubk#FN4N zTu73mBji2JN3mCyzj%nE=qZj zdJ~bvqy{WWlLiCl15fO5umRdI1&GD(MV|-BH$3~8%;>eoxVs~+;Cr*N4J0|%3LoEV z@-If=B?);7KvyE(kRpyH$@5V%WOKx!6VtDvQ4dvSGn_Hc167&f;4dJ$UQbnLLu8Ml zE)b5PQ=AjF3UPjo!a9>}?7JPqW2}H~jO5rSWq{Yu=>0}tITkwnU&Os-R9suW?v1;< zTX1&`?uENUfDqi>-QC?OT!KSzclY3d;10p%va|2$-raq=@44@X_kNpW)mm%Rlr^gU z>v`ty!8r+*WyRL|Y`YuhJ!~5ns{h$wH{oSk=Sks)%MJ7~4tzS5Ak62x@29w)o&yCW z7H$meIBX-3^jQC0W`xOmZq+(dY&wwzdeq043`@&BLy7`;0a7W zDG_}b{T^I`O%T<1WK2m|-_QHhvrx>?# z;ODNBZa(1d5842-F_AYu$q}S}0N=xQiR9Aw} znkZ)iF^OOFF4mfi&A`j|CpSD~vvb4D9@7gF+JXAygySml&<^nT>&Nus&BN>=x%+n1Q~Me(#{#25>h-oso673@^gfVXg~= zR%l@dri@{4s5@i62g;leo4wcHSKTtb;Gr+T9*|t(plek;(a-U`keL$Bjjiw&i)1@b_FWMR8SzCodY`wVv;Z?NNiC3pe)kPFq-Bl-RK9*a0}a~tJ> zzZG9_i`gA-VmFpsy9+(laK>%dt&#m`hs{FI#LGahz0q&;v`C?PB8AulUv(f_yRENw9tK1;_+ z*x^K1U)rk={=f}*d>T>sVOLw)tEaER+sojMfu}-j|BaOF+t0Muqc><4!pAoz6xaS` ztG9YzA&mG<67_xw@y14URC5aTesRg>-!0Dmq5g*7Ev2tRgRkckWLJXpGiJotON;Vi zMag1?qHQkLr($B-(g?gOr+>Bx6424U|G`X2a4T{-S^jY_jfBC+}*}ihYvMgJs||%`{)KDtccJ!A;r35DJm62YTG+wd&`M7AUp4CYysAHu5Pt>2v%Je}@5xB- zsVIL2V!czw{{T#|y<EUKNuOSqW0+D`$!-pBuGQ6 z3Rj4_Ym?`{E=$-Wxj&8A9DD-9-+g=Qw;d_=w#<$u5ga*2`hc*A+q=S4gz7<1g)R;| zc+}$ju$TxZ!n#)F*k9%xh^+=mVp};GufE)Yx4EM~OovN;EV1m(MR>Q4){88EKt>?0 z7ue%Z)lqSgPYID;D{86PY|qbpy??I_E|UKABVNKY&`}Xjx)oL2dAB_Oo81Cf_CtxS zs^CEznF}@(l}qaW-uA`@dg|*P0uY;QGBGtSun1;x+fkt>R1A0xucU74@UIWSyY&9| zq57+5;ct`t$H8~3govS|$sceT5~hD%4*ye8@%IA!C$PouiTa0C`WM#Be~F6htStYB zV4-(w!arz=jo6k}Itve3$Cmrq%eCjfjvwt`qR{z}1=0C@25XH=$gQt~zF3}eI^$hg z{9tDfjMx_nx=>W2`2POB$?W)(MSR?1^voG_l>c{ggnW5pBe2_P-Xb|X6<`AQ(^+(4 zOW{}Iz^9+PUi*BTJeyB#OYcYvi%x6z+k9hu?#-}!(Nn7X2;Ytww!8>k=-@-K^OlKHo0K{1xI{Q&T5N}T@*Xat zFGEl1qu-F&onYNc?hbh%a&c&$4=?p?LhFK)R~JJ?$>MD?@#I{AjNc(|KrT1QG@O#Z zck>LIahB=hlz5P!u$4A6FWh}2f)j7|lu=%sohY5#SSXSRd2F6m!?34GWCo}O(nOwn z-eY`NV6;Zl78c9nTMo?)BPZxM=yUR?unlW&VcorLbYXQniHk*~I4r*r>y~{wKgrgp zrv!!$S7co0pGY>rNhM ziN7XR9MaxUr*4yLFfkPxVA~_C?}?yN~0t5m;Msa(0isly*Z6a2hu8>$`_U0r+9 zM2CcP8PruQ@xinKP$qgcga3V)@puE`4}$Bh(UwxRC<->!mX{ zQdZX=?zs$n3xL3drCA5p=3+H6V3Gr`SvdO&0i7#(N=Ak@5Up0P%sw|Ug2EclQB$7I zS7N2#2zO~jAwbWVEGP;E8{^jnrL~S4r6C35wAT<^&64cMJwyEP@TzgLn=5^&pu@OX zf@?-5HhMK`eYuu;i%X$|QNDGn-JM8%g@*i0u%*e*WOc%IbDJY&oER)d@xu(IatLKiQD(zYEGrZfj%68LCsIkF5Y+@}rBT zxZz@ijkvC&&WtwC*(X6#J$$xab2~W+%Sp8x38l#r!=tkV(P>9`O68xE06YYaC(0GL zV3A>N%r}LickOuLVg>OR1Ly`%!rWP8a9l8ieMOsByeE-{*3{>AZqBr73Vr@;0=D~h zyf1_8F0au}9@JYdf;?uhGYw$R{LrsCAg2g%m! zdw$`{#~YwIX_mI_w?U=BwYZ?26yZn1JkD&i@#78^6lFmS5pFF^-m;#qTzMP^TGDvL|-^z>QY21<#8#x-~J?F@FVcPSD4Tn7lcR%iO z>tys{(98Vr>(XcmF|Sq2k4V!YC1y`Mvuh-zMY{hm<{x zk8hw6QgWDmn|T}!C5Dq=0p3ZQ+z7Gr3dxuVs5Tgb6hOJqd&Q%gsPE9GEa6eBX^76i z7-U|@cf>!a4@yffLZopn%rD7cM)(gulG{HnUlX}jjF325DEK4tl$MH;h>tAH%e8LP zOe|;qP)PnF$4C^ZuPxxif)_nhSQ|MuRwH3-FI$20g&l*)WS%xT0~ayG))WWR5SX$d zTLKL=d&rea^8N5;izJUu^(3ut+l7x{$OuJ^FZ6pV%HSuSbkKF`Fmf})#tjU)vH>z~ z4sTKkRm??}cVz?NgZ^E>)Zm8nEg`PT$Lm9;aLLS_232j9z%l@=n-i`TVZc+~n54{g z4nw0yh0IyQwOr=P>|-(}KG`#D0sv?cyTsC87ah?1%Z&SZ^y*|R-pf6os{)4xYzvT0c=QV#4L*=I{_BNGu`Ks6~91F;3O3_>cq; zSBRvm%G-tTi8JdruFz27KXg(!IyjDvi01kPUjxaESi-F%hT22NSzoxvqncYe*O4v`|)$<%-+GtMHh2s z*F2_zajSYCLL(Q28XzQr&gB8FNzh32C|_#VKj&qtsX5f6aE90v%$T<-MybrTu{In^ zr>UOg+psh8Jr>o@MVowo$3s4oVMyE1#7nxXrKS2O$p;$0LP1^lvsmQ=w!)6Bb#+qK!Hlp4h>CdbNPgZ4-SFu4h+?vny=PyB<;Yh=@lTVCSXzdlebzu!Xq%LDbF%&Y%yHT{pP%s;*5~ z@l#91hX)v6I*}WP0Jh~?dx(yZQ$IdL61*cE1hbmv6Ui_hi2J(u?U~wiVGIym&_iM^ zd>pL-pGTHbUnmq;$f=K+zgFI2i~= zH1xYd`9d9c@lC+-PgZ+@>}$4|5U~mwdSN!gE9x&5H-b~QxmxEDn;<(yA?BYjQ=;pe ze{s>TYx-zfp&8aGqh(T$i7ZmQ#dgBgS}{t?Cc6y@TjCu&k9RMz@Ws8q~+k!0lx^t*~7k-~;6r zN5(@d!ueSeSc=f91X(T;ZHB5HE-~%V0BMa$L%#O40j3!4@XW{~o_HbeO9O^;UZdHL z-Z;xV{IXS1(CcVA@iV@st%wYAzpA_aH))FFt}`bEiV$e#nSUCfdatJyyS+! zGniDlo0@dqJ(z1>O~^{j%G}!h+NzU@6VVfmAItVlErj;D{oZI({Wal&=x<8T#5-?k zaami^nh6&cy~z6$na$=K`R;EynmH}hmt5B`Z?Qn@*Mnzk#oM5<+J};^Ca;ttIR~mn zazd#`2AYwB&R&^CF&T3Q#H2U3uSMh~sv8W}B+X)RRGQGDR;Or>at$#(I=%!|D$Kv| zXLu2@F2oup{c?B_WSF(WI={LZA&j2k!|Adf-^a(}?ybAQd0Q$Zk3%hqtn;p(rstcx44BkZs zaAG#Iep&D|xY@@foLmYDpqPKnNj~>|EMI5``Cx{AGj&BCkOma>`1%=D8-&51S=>O* z{6iGJL0{<3{XBLM zAF(gQfvDo!E2c|?QPmk!?fCZ0EnMQ~FCda2jNo2O-<#mKbQ^rIwix_OL0!NrV|qL> zKk*AObj$kz{K~alLm##M3PJV@jbSH|{6s+%5;;I@z}k_SajTleSGmC3(bAzc#P{8x zw$Ox3onA>nh`S-W_5Fy!8^9_-Np>A z17qL(dB8y!{pLWRLE0gV_SP@DVkMc8^xA8z@wU_(^zZLxP#67o=)T(ukAvzM-*)R` zK|k?2l;D$Q;m~V_ z7$zl6#2D8D(SntQUnx~;SBxlq3;l=oFYgEK>QzO)4(E=+b6vYhfcF@EFGhp=dG$(b z+pJqHBP|U8NY9L!fGVhU>FTF3b95C~o}6sed2@Qo#E?K6w=^tUKc(Jg+ovq-jOB1V zSXlduY?wDjWo&Z9YSU*sxchhJeX25DSYZuaDxI;qd>YEM;f#KI$>hK*TvGCY;}!(V ziUrx%RrQ-LT_ymod{!2FP>gsO)B-%{-qQb?{m@c&uGVEMMVV|2{P;FPQ5{&7e_ccP zZcn|UAKP|dnH=8YN{e2_NB)#u{K+x;&C1jF?(?sLp{wiudz8tEl4% zN+7*lZ%H3L-h5apJsrBp1xf!dZ$~QkIV*mJk6SqgryE9N&|Sc7Z;husnFH4=L>qpY zDu51FKn#bHDPXfVxV074V{&_O8rOsT2F%7-E~lm5)hGa0K=@(`?6nz$*ae4+(}=mC zG0sO?Z6 z%ZA^fAf5DNR9o4D%#yrcMyqFfGHdh-Y0xdNCh=ugP9+AN_n*j9br4h5K~oq)$KlEO zED;SXJ1;ag8e`hz^Vrk(c|l``oVF@YvM4K@H)nyKRg1+ZS@co$j&+$;iybp5CzWH| zEIdFd+GB?{J`>g-h&cB$izA=2tEFVFypCp4kEqHky2|FX=y_kbDk@@^x>N-PR`Wb@ z@Lr#O{%FLq6w05sNRA+?FsZsU*}rs-`o7M3=;e#!C2h4V^nr|Nbu zrNkl6I%z@OBDy|tT9fYGLlx?V%S~X0cjrhlyn<&30|U|BCGT9R{C@WbU?fBV+_gC6{4 zouNFu3!a=D$l8j=mWWYJO~=>lvA|O@Hw03^)qQRHZWGM3OTjY(X+U1cg&6Hu>IQ-rf zgiaVF4Vk4V>oG0^NzoP>p8D~TW$#jN$F;L|AE&mjkOY=Z1v&i_W&?K_uINF)vYsYkY!-ho|@Bue00wGWB2w@$xT;xNpuY=-ThE7-y;(;<+EZg!NB=iHnsh}sU0{}xN$bZrh(=pKIaJ%U zjy5QE@0eabAmP4B?QLDxvCh_EWy0}FtwH%jVtLI_*(R zqf0XHYa*3eV|(X`XrEu*zm75nD4uBFeW9c-NitYosZ%J1(E zevXg2nWiszwAVlJI6fcJ%x%O}!&XeNP{d1z9;aHTjBqwDAWEB{0eT2MOTKydPt~VH z2b?FjW>%Ej2=rMmTeGxj^XI!K!)JZDW($fT6q4i_mI5=yS4D!LKBt`{=j5l(zbRFd zG~)ktb050LF}UAmK}1VGJv2j~x$fhmw;FsE0of?9d6%Mo3m}i!S`do_EM(VKx!0D= zlrPy>g;{D{hji7Iy?ekmTz+MoYpBl83DT-pNFu^=6w;aqgmZ>=Q$4Lf?(&O<#uriE zi5Z4Rm+1bew$&Y=5}^a4DTUTsG3TaQ4=c}Ce2>+xjG2bkl*?VwJ3dB*4PM*rvk$Lq zff$~SsVZMbToWYJ9^MV%Z#9g;^5e0w%8HGQwk0svMSy(f;T#CR3@MSRN(rw{Aaa&R z)u7O*ydAFi>CjcAw_(|cQ0l4glH_3^FU2%WPv8ViS-F@%!!lFQq)t5#1Rh=mIrA3C zTvjsH-_w+jH${M^>&EwPFJFbq`7knd%-i^u<;GV}sRDQR2F%iPdl~^EWy*Z1W3dOk z35l2i6Y(9qS-Rrw3j~YQA0-+H_dxFCKFLS$JpjV%QjZS zmW60F`Ziis^-gjb_NDKmN{CnjMTUcf$EFSVWm8^55(ERpK&bJxGts4kY7{} z?iJ|OBRM!Yq?5C=bCWW9mlYs(C@By2YlT#_%SV){L;<-;1tG1Q$P3evhG)7EAwNP| zyY!V@mjVauK+jYFqc4i=TqX_dI!#^Z9JlKU#jxUP1QjchLIiUA|A*w`Lo7=(>GJw4Cdk4hsHC z=oIGIFSF!){NaVz`{v5ZDwKx}^QzgJnn@W6yfle0soI8o+cXL2!G|k9Asb(e27UG! zzA>nQXnh9ImT0oRuxU@oe=_-5Qrs;ydjVQiPMjRx&mEXp@WZq$VQ#i|jMLKaL8vzF zWEgoGUo>0U!-jS}jmp4^UW*bfg6#IEZXx;#YodO^4$wtky;_L+Z__+b*X+Lpqm~L^9@L~E zavFthHYUCW!ytilZW#s+yTwCFsjgikYhqRp|2XCmli9;Q@yVB>@7Hhn~S9Eq9g zS&|e|m4}D8jP)j5y=l7&?3J$<^~BeUKH$MJA^X}`96DoYlLnHrzGa#HlkIDmtxCqrZ~24ed~ zVKluy?1l*RCM30{*zD?awn;IizmO2YVcFsW6={;(o1Av<#3ZH^g>hurP=F3shf!lj z3VZKbcHSMxI6u`8)>*GgO{h=8AneOQ8}t2;K2sI=TD~1ZUFaq)2raA&`G!(pzJa&j zT_~?Fl6~V=pd!`A8>%zhIpQnhJ?wT|g0dIRDf|L+Y>(M8tOU$Tni@xe zdTNo%)tyKe+mQ%yXXqQyBZr-B>^&CQ^e0d)$YT6`XCcS6`EjfsSk{6cy^q5KclpNjTXeI88zT=Zz3vgn7eG5Qs-Pt85T-B&w@(|^cP#UX+_%yuOFvZrGywg|`YyL3 z8d4r8UAo`ODy=d-XX?My_nq6de_i7$QZ2m(&L;2mVp-29np_~qf@0Er5h(0}ptTr5 zB5YHi59lJJeCAIAT<*lC#O_tF1JWyp4#}CIipxhwSnq_f0^T^(}rv9p1y!GysnZ@oEdU_3;xQ(dbYkL6Z)h zzIv$F66fL3w;u4*txf_I@#i>0b0=QojS}q03{HNpTvUef_YaANd{7mQlku+HlHQwA zV8y_wzFV+jq^9?{&+jG;>{0ybqNmH@H1e}j@$x)D{<*`~ij7TkPEQU3_jFxt@Ur&gyz+VFAz#Ixw?@p|XkBMJj>V~w?e`rNU zaJD|{z`-L@7G4T@rZ;g?QN=F5!W;|`U&dZ|B3ONXo}=acaIawd^|?ZR;}!k>HTdg| zZ629wxH{NI;1kl*>yAgI|7>mh;PU=bBb#0M)mGxn&hSt8br)^ z;m^pG?5=wi9qH*l(Js?7FCWQ+uv9Lu^ZKitdZ_RVtXXwtr~0;22mmn#+%w zCF9nHC#^U{kW*fr=?b|6OH9I{a>H$WAxySuuov&yZPpp0%SuK8u)KQh2_7O4NZvLH zok(wA)B2iNM|X{O&1}qTDu1v0^d%$##0HXWlv=S3G@pAu2I1ztgYsz)3MpViEP#1| z#9)+r1wP)0UK~|CGIjUQ#j&6URGW8WJ+!#%PH#Pd(9%;wNja}^9-%iVor-G=(+tXZ zA>$~kn0V!e8)t{FpximGFpFWUfb5~w6yJ7^hj@c3P3MiOTN4{Br<7EK6XG<=S$j}K zIVHTapVs#E>9}`iZMod9Cy^H4%A_dI8!{vj0QSM}99u5YtbzUHv1IrKK;PE^g5rFy zjqR=*#GDHHbBxN7GPZA1$O|t=Eb9THpoYr3i3B1{0 zN0O67GewQ>4;3`K_;0x0NN>?0jzK<27ij8ArH~NpfOkDpYz+9^Pp9G|59oms*qRZ0 zN1l>A6|ACR8ZDQZn&6_6Zv#dV9kE@GeYaCMOum?G<=573KYX;Q$i;Un{#v>+flLT(yk-$I7d}w(-zNb7y?pM+ba3u8X`zugyfO(h4?qt z#UY;)K@)Y&<1?94I|)Bpuqv(3C34w#r3^kiOB`H!_y1rj;KJomk+I|Y)NOpZqltYu zbEr2TGdsIg%ypUK?RI@9ps?>H{I$fii4Erm&m}>EXHJoNCr;M#=%Uwyz|BU+iu@bt z>?#<~Aw&5~ZUB1a2-BviB?>&|miiBoGBGr}wl)g# zQ2+_%36@Ne0!2yj0g%<(0`4K$vTWp@KLFq$9*$wL?nfJ5Fj^EcpR~KnTn-v@mT#S8 z!hMhA_-%5rP|<=NARb#axmZE2W^%sc+>0i_L=`&3gt`c8{n1ArDc2PK%lxKjhy_He zsbt4Ak#G}yKAv#XKAW_O>#OXQ`7`Pq(Th>Y!{H$oy++;ad%5!-BJX6nsUWz_6?nll zo)6xNT`%%9aq~JoZvoQ|9rD$7U||0ueY!4if5hj9AK?S@cp|#bz8L;oG^IICQ9~Mt z2EwPJkvk(6IX&C8b?%pntIf{U_BNgyE{6wK+2qjIM@h3O#-Yof_YaE5KCjYit>_9t z-(gP>q6KlS;iJ(rTg5x1-gvKG@+hw4KxAyQJ8}}P7SpGZo0OP-yoZV8Tj53FzaUq0 zOpE`kqQ~}*e)^ks`ggzR-wninHBHO@i$tz$V(M&V=Hk}m{NFIXm>GF~E9ZZv zEehM1TA7giA?5ug3-SM>O8%RJ^H=5gFU*_2_x`^zQU8YJ{M9x4AI9SUrBMC{dg|W` z$#{G*RLaqL|vM-jh!g=1P352__ap%EEXF`5a@xCIa6myX8^B&gdu zxAa;UE2oHyF+FR?Ns#NPvgW(kU3Y#I0vBvt3Vq){H647?4Ou~JbFllN zgZ;(Qku(QlrIK@F18Ha6l87kRSV0#`UMczvLGMW-(efmHS@~Y&^5;fwL_Eja3F~MK z)ysOz7Gp;nik7xqQZ@L`3ZVyqxHX6j_-UjhDCLVRItArn9p<$S$+&feb*#|&b!fcdCqLnE3rwRDptbX`!ilhIF^uC<@# z&RMK6$nqxgFiV9SN?qzLueZ4WfEKas$SbvM`Jf>Q>c?P2vn&11^Cz$2G z-}dmIH2MFqcK^q+{O|1u|45Yjy}JJDL1F#N7KQ(k<=<&h%>M^jo`s!)Zk9j>$4PP5q^iIXCW^0QiIQc|YK5W_@hFmZ9genMo1lANeLBwz($VSZs@ zVbjteeFc6JcB0}!gXzLiVFxfUNUShUKi}lD8O5^F-vsV9-Zr}0Ha#|Z)try`o?PYC z0+>aH5gEkj%Nc0v$@g+OUn5ZTq4OJmB_3;s{(3X#KG_U-Q8+S{W%EvadQ}XqdKA436fPp@e%*&Xt~aG@A5 z5Ga_3fGk)v5ucRw_?TVqn%83q=DMN$*a$??5hw$8o;|qcctDeeLrku4wA!dwd?6D+ z*x!ImE1s9LxdBA^5|cvtK{qrt$nE%wR|r2P?o_GZ>wx2vZF`BLi3}T`|~Y80vm<{@KBXK^Hjb{PGSi>%Qe8k6haH4^y<0l4;hood7D0-0VrR$ zv*y@$+Xih~Ypg3hzcYy}_Uvy#c2W19Gd+;J1Aq-aq*K4(xcsy;cr>JtbHmi6*k5#1 zZ&5=rxh1zSiqq{WErJV){~Gm~6z#Mq*U*F#z3xMzw2XD)0&|oZzi0aO2c8eQ!n2Yh z3sM0ju}UOGxxwF10(wb^t;|sRS#i|zwb`L3L^EgbDH(S0T6r& zXA3uqHEAArN)vrW(=k8y`0&*ABEJjstH_5=EH>=OBz2~mBj zt{{r@Si2yWO(ar$6U(fZ z0rCt7{Q#)jf$#3wTQhRh`1I^!^%eZ%*^Rh^;5GIGURl1vX!hw`ed-~L4XpCDoHy44 zHIBN*SY8fIl4@_qwh z2$%-OZH~<(nn8NNRRzUKk_x8wrjO~7DuC2rOj6j>`)dwkp5E!z<5l^iX+i7vc-(;9 zf2o+6L%Blmz!J%A!|nv_pw4=DxAXzHzUfuwESfE_41^IYR zA@Y3>*8Qu{Fk6D){HXW=iv|)s`qfr5ft&VMUqVgc6K!yFl3SHk1FT-N$JaDohnr;2 zp*O9^<~6>34yh_|=K{uy(c643(w}W4r`cbdv2QBk9+z}^uAm6)g{r3=@8C>ryUv%- z1PM+hpr?hMU^*Zf%J5I2#hZ+lS4)Q)74BjRTuFENoe&5mi3`!=c1(pkVg&-4o?5rC zbHoh_Y^T+n@UtaT^9Uw|({;r*^Ur5unTcEBNSCIaozO=Q7?T$C%vy`GjPi@T@(@FX z%~xJMwXfcWO;3pf)9mk!_fis5*N+GCUTO^IaHZ5XIZC}b4rn^D0v2Z7$|Bk(UV>D0 zS(P1p#;LryE@=*%rAQZK+8=ESP|XNLZl6PHjU|?%@U-;Ql=RiLm37&@mZy731L5BT zD;(?USx<2TXXl`hzW!2>lWyuC7`%Z;hUW-{!2QKr)g}Q{9X3%$`a+E}u$yshAs2I@ z9ZJ5YtB4|VJK>C?=xQKT)dwN$-X%C>SdJ;r_VOyrm$h=!o$lgM{IZd*X$cTDuVpFL ztsk)`8?rhY{2)^}DdnwpuM4+;q5 z_Xs3+vuvZ|b|oaEc&L_8twV<4WX{(?_*$5&R_0&f!FDDz7~cenCNaqn=UxNC$fes; z{+LDRu8Os{zD z_B(+EN%)wqawDw~kFVUD)kiJhCNXnxRslLiMnPk_Y>UY-wUdiiqz;jP7R zr-h@MF$WDr{UYiE#~Pz%OQ9WR(W!s^K%(@hiJ^lE=FX6M;%oqj3X#>gmbsk}`~byC z0@?(CP0dh{JO|3vK3jPH{D(gwCw-eAfzb$X1=6Qyq37n>R`$wqr9YTw_ig|f?B~fj z^Ykn;cd*i*C#7OzJn0Sf%BVbPl1JC%YeOY^)$45BA|fAAFZ8iLM0hNi8L9v^K#B%W z4oTIjoRur1VnQ+WWI&DFn!G0Xih-UDJ0+?T4X~;vGwRzc8GF~|Iu0a|1|NDfAnz>JaIZ*BTc`r`K{XHr}GY1nNoKQhM6qkwa z@av9=$%l$tOH@_{x|*ZU%!zuLYU`+QL*=)bd8XJRxM%>dk(lt4xHN#FBxBZj^JdJn z$abk149e|s8w*NS<=R(jQqra z@2bJi%vGWE;j{DWOB(u^UcKi4XXg2OyzXJzP%=DTA4V&ftW*U>_@0ND;9t)DNUBQW z)yct#)ioi!bP_Nn2rkqlP{DLi+#eKLLgvL=N{L3H;1pYYNKd%2)cMK_N8~CfRpFhr zj)friKow8ey{{mX$@|z9J^8&7cS_2WP6y$tINbTzRk&l-FzYIkFEDBU?{c(_=&zj)CvCD_AzOjB+?(TBoox6;~hKnmTAm;OwZ&Y}63k^588m5k}m3iiPAH5L|6C^XeBSE z_dMl$c?@jv%PMNdupjtJHxWN9m9nr0Gzz$>BDx~pUyj|^Q+HwveDQ}_=|B`eGUETwkxh>3z_@(674T44;PFryCY9P9yJrH@n*jRkJ%7*kb>wEs7m9Q z16E4oXallJ<0u2{N)6#g<<#0@EaDi6;w;h_$l*o>)RbWbS{OUxGvpBxO6*|;`WQQ6 zN>mZrN>P{tI7(4i;?dL+;RV_lY6Uo?5dbCCumW8SU;#C%crA5=7>h1OpBRf6Mxb~+ zRRq1#=ZId$2n3~~NTWPztAYcj2vwz`FfuI+>4L8jWJ(yY1!361;u!XNVxNmyV+*0a zz?KWaG^J@Fc*gc@n0_4?23y7IwgG`R{Rn5c6jTM-)CP)Rh27&rx55g)eFSOe%wmNN zhEo(=MK}%jDcwCAF&%S#{CRS`Gw)?wcQ1Z2gye1!7}mKI1~r|yuvs=cO7842}Xxug_UjVm2LY~dpRR5{q}HNH4AsX_Y0JqA!{II zKc_}itaZ6rlyzxoETM){s-})%*+i+yAZ1J$44%6i`5f=nalEXFmAJSBBx5W?#V~cB zWp>}gSeXts0~B6B%GZLXcP8U`|?^u*78Y2d-dHC+~(ID3jD#=oCNOoW!X3SUlG(sK^AYp~)_}!2~)cy#fxIl3ryGWdObf zPARk4Nt24aa)+uVQ$l%DG}%QrxIhIOZ;?X?U@VQd*r6zZSkWVQ)*diK-62^PI@^}C zk?)i=iwX#(>5w~Q0EAK#7CHIN`T*9m2W9iPXif_oQfG+(dP%%A&Wa9--{s7)fn3z5 zMK`=a!X*8|hV0pjq$VYg;8}G5lgf9ILkd79jdejo{A^?rr=o}dtOsC9MO*Ta2VhCV zFLQ_su%zafGv@%RC8;ZWM9jhg6qL2)%R1)bkZ&M>CP^vE9$B+!fLJAM*+V5jBsD`( zgWs$d;F_9W#GDbxlk`O;L)@Gah?`Wbk|9wRJ*x^pRGXLQ*+z2c4gUVtJ9Oa9NfzE>Iaj zkrY9Lse~tQjswh3f}=sqXHQcG1$qJ8lN@M%DvlN>iPIz|WGh1fg_CA!fJ%5$<{%XK zz}Ia39Y19npdz3+No?f%p&CMx6pcUBph}%$L?Om%StujnkYdEy4FY&X0mjOq@Z+ve z5*c-{3S1!uU__QCNST>JrGyceI!u`a=qGQD6ema_20??gy2DJUo)DR?MwDQGBS5`xfV!t;Y@NIi*}L6Pmi&cMzVr@7uemopax*C5Zv(hV0MF5><8>y7+a3r(%q(h3ZS_l@*o0GUJ&;v7oc0- z-S*uq-JX6#-EDrZAd;ZB&XS)zaV|Kw7`jQjg}Xt!eT2BS{`4) zFWW4F1@AYs?)6=|6|RvrZP=XKH1*kx>o>`5Ot#gyGzqG^pKGeQ;lk^;LnVo!yNta` zLgU`fo!3(bsqA0bl^g16v2*29)i_ z?Y8c&^3(M5{oeiV1Go?W^>rv%`GJrB*GC*D<=n?4w_Y~V|1Gd z!0MsoV#$MI02YHN1B?O|`XLE0=0Y&wFu*W?Fkr-j4+9wio%_KEaOn|rgB`<*gBAe9 z14;l;6ClO{`~p`4urJ>6Ejsvg^)CHtf_<*?j@&*!Mz+Sq#<|8lOZ#l9|~9 z6)DYBB18%PbNq6uy8L&JP;2~3f43M>f{8;Wh#{m_W)xF7TGsVgwZ}3|KCjhB1=>Z@ zk8{Hs4euy&q$ApklO8(RU+`>oJ>{61yy~iocas!HORR79M^WjHM|J?d{&#I}NRHEK zQk%h>5E+_!X>g#w#S=GnkbMv|Dp1_FD?C{h3l+olKFyXuCGuJLI`s7w_dwN6o z9ZC0~FdPO#8gw%+7glKS8g(uno{)BHHlrB9&jMt)I+W3dYbER>SIX)|yY67cR!Y?*C+`89HL`|tvp){Jn}KI}RVmgXY%=PrOq$yY+QEKCP8;S5OHr0_# zLcp)%BrV(h{izv*e0Moe0-0UT!w1qQV}-%wh`ElMHdf^7rY!|OEzWBR`gRYPLJ55R zXD||1fusXFZ?Z6Juf+*yFpwj9s`P=#yC!09$JB${a?mVvh^A*tH&-U#IO&m?bZlLU z#NqqmEquiRcw8492cYXRsH=Q#F>krhpm^JjOPlB)qs|=;%ub4_M8WeXqOQ;E#vw4u z{F!vZUF}Kc$c(!qZ|N!{M%{<bvc?ofspx5Wod7e9 z(0;~|N&}NBI34~{cd4=N=RY#E*^G>F8lUK13=Z?ljEpfk-jzoCW9VyTY_Q2Nk-zY2 z?=KqFZTXYQ<0uQ=?yyV)O>=eBKSw8M=^vaU;h+ei(<+k-gcjjig34R%prn~}BRCqU zlzhUBIn8EPei_Mt)vy9v#;8}~=fA+M0I3@WS0r^ToV-h!k|QsUD04PB`b^eIlZ-rM z*@Xi4;=W!CKqdWvTj`vrRFqIbM_s`QFbFOe55jNM;-AasT@t?KSy9Ox>3Np=L^&JG z>8)|~9ju8bwOrbK&}xF)-2ix)2e_e9&`D7o6j-#`AXlK=hoEkIt8}J9t-b85%5%en z92!AQ+YW{Ph?`8;ZxE_$dRSU{HH=s=AQk!0A(DVWn+>Q?Gk8vCcXHXiE6Q|D6PW~! zO;1U!nPes5;Re$3uFpEWtR-x0=bPbKxR7;!ZDQB31iP1S!o2Tnm&N<&Dlqi}00aJr=>Fu=n ze41n-lwko;iQPkq>TH<@hSkT&Q0ZNFqtmfk6mJ;YY!6lg9pQa>c_eEcB8}x^y$6;e zZ$aU<@0Lg2F1OLfi08b4we3#DBO@P}`y1C6=5azhn_TkR2kj)aTx0j&5GWoHC&Aoc z@iyWsQ%B%WGOw<9;WCue`HCnq)z6NFtCAyEUt-W*^buIAw!9iH?B1s155UQ#x!*yq z0jp(j{MTav2jX;YH|ShjbVN0xMQ!GF6D1F_JHn-3fO$txbe7}|$#}!{^qM)E3ns4A z8w-(}7MW1&eHfC@za_vv5oSKdYI- zCe5Vc`#1HQ2APjQ#BwZsoQbO$)_@QSd0-$8*iuP>}zeG#GtYa!yld&JbQK zO((NltsCx0#z-uC6CoV1`4Yrx1Ntn)#j4q`vayGk@S1Z71I^=S@~sgCZ{BO6pMh=B zlv{`KWOOJJ7jP{ZjJJh3IUk?-)4J8$eMOYj*oI8sAK0s=2Xz;k7JPfrp`MR@ik`~} zaWxt-GD^2(MyO|n0;Os-&aRzd7G?+t2T6>|se%=vM zjPs*w=y`1#BX_C#_#vMCy}VXJSDjWdqCk6^?4LGO<1)V&gvhj4LyfORZs#>?3OH*l8_y&dHV{Tc2>nzUNP^(0jGqHY8RtoNpTnIoWQ+BbYU$)KHWXQUIE?6t#RJ*T~fn0+8WZ+5c-})DpwTG*!|aH0MwJ4 zJEa|hdwC&b^zk76>IUk~X-o}d@OCVMu2R@f_7HuIs<(wEj>A6%iLDL=1M!$}&W9ek z5LRg7HbERjg=!Te5(ROi@!&N=_+^H^XYxJT@Ck{XY^@Hm3Tjun{25#wyGf$q3eC;%R=oW#Ix z?-j6)7o$*J27p}8FK^09F-#n5*)@Uf{I0AUQ6O#ZI@G7l^^ri_BjfQiK_UCl+n7xc zrpvsxVu#@C;dI$39L)tE$Kxb$=@02KQ4Y@n(=?g4%<1nRSJ04*Dy;EqY^z3o910Y$ z32dGyFi4)tmx-&yz6sX2t~gWtsN9KcYA7s4GFG3a*Q%LTfeegNyM?^Trqd*N8?VHQ zmAsgN#C_Or7xC2G6&t$TB*1OY?{v8Nl{y(ZXk0-VX4A;m0Qp46^ZBDwBd}>B`|Yv+ zPr$&2?u{++iKgq1^sZ2x3-{8+;&%&`O|GU6jzO60BL;j zbVBDqIIXh|sp!eH=f%<6 z@)g@elO~j*L6jyF;@72phCfvy9wlO!xS8$PuB#vG%2W+7?%(o*<_s#=7OkTD_Q2GP zRtICqdk;OXG_sX)hGw%E^R8zQ7mpxy?)}1)6ic6EoXm1qSr)ypE#wFOU7OC_KwoIn zJ=%%8E3HH=h~^Wu3u$9MwGP-Swp(i>RQ>NQ6Enhma6g0Yz7=9aNQ(;5jv$a39OK*R_(X~x@0vM*RqoW@NNM~ZHy5Vj7U;*bIOb+r)9^(1^{t8YK zFJV~TaUrl+>w;45IMINXTKc# zjKwA4np25JH&h>ydQEBY#0x&NFGTr9$;1Zyk(&d$U?9q^L0y005h1|@-*Wqn$^nKX zdV@qJ)!rltEmGJh;y#txEAI=!arMibIj)6-)&vRBNj2fe(az9gIVYe0VhT%c7?IBbY&aWL6{oLxrM z9=;VW2v9SvoI0J8q_qha!G~3+vW=`qaltG7@VY)Eb=V3j@XsSX7;HB3oYx$ekV;>f zNL9=_$sB9XN=Lr@BZrlClsTruXGJZ`AZ`a&<}7CbwhSB;ip{CQ35Lp zhAF8`y2@Q^pcpu;M&8J-pNmX8x3P51)ynn(^rPd3k8 z_{M3{Zy2oIdQph+cR*%||JOk)B$TY7mmC&l9Q&No=6^CKgCk!R)Fm0i{+s=d$ zfj&>Q7o$A7>==5I^~cC8KLnH_;P`L3E8HMgP_r@L4sH3;NR=E~HV-w$d+Ng>(-P26;{%ogmRYD-VC=s z^cPtS`8;!_&OR;iaQfj?6yvOh)J*+>Wo@~*w<&Q|UNV15WW&DKV#-hBR_-bLvX_q> z*0*c!6NxgX#ih8sB~xK3Ceamh8S#|^iJBT-OkY?|1gZDbY*U5?LGlQt$<6*gp;i)WKD3k9kcvZr# z`TlM|L>$%G*iIEc3v->zzip5=LFQ`4T5xouI%?z)*tSkF_bFU4kCMj?_CdRN0HPrW z@v&}R>qh6M5b%bRO;gTN1XZ>{xAYR|f5*JV)vephH(haiVqI~g1m4nOsdJ?_FK6gY z!z$10`sxv}Y8vh_H#>w{ekL$g@&^%k7!3Nc5Ra;a*Wkz}D{VXRcOY>_c3%-&_? zf(HRRY}!~Gq>?*R)K|Ln-bmu6jlB87C0F5(KQqH9w?rl7K|Q2+0V+llf^IA>Us#pj zpz&s-X!>&XFv}LM*N>ldZ{tzbJ_Zlb4l&0?M;#bvd^lhxyql{mVff&XWm-)j zl1!c>P$ujpobIu$;^J9QS*HwRZEu}T;OP^QBQQ!HsngVn`;@wI(qyy->JpHH1@Gc~ zv-sLP{9sDw{k^lLyuB8FZ8_Ss5TsTUg=*Lx^A%#;wk7RFSP$f;kkH`!G=db(;8R3L z5Y#aHID!;%@ushr!yLNe!4IgQ`Q0TYOcQl{D z`0@Ei6G|AdhL@mNM}LK1V09EBrtzR`hrK;}I6rxJpz}DyAlX%^wY4eEh3=86X`GZS zVQW8(rR&!{xK^w`xF+S-44q0*V+{)NqN86aGSXH9_=w&1#n@IpMcv-WLj9u0jyK%Z zjJ{a}JiR0pkD_-3CS+J5A^DJ(7p1tsVA> z3%sKf-p$xPR30I)M<&G6g~-^9J$m&_^S!%*IVdCN^JkOqG;-&M+;~r8?xzADpTb9= zbcuqfq4H^0bqsaa6%KL>7|r0>TO|{@7G7*i;2k0TPFu#qzRES5ebra!_GUbr+c{_vX| zTqL}UOYCgC{h6X1u<-X$QtYAASqP3Ene_bt)>ld{FmqrJQcK^D`93t{zAJzV*|~J= z+=DHFCH*-z!R;%MZ0CrlzX8+2T;1>rfVM90jYu{)+Q%wTlHwlx<+=%aIDCXYaj(?QROQlYl{@%thQWyOTCB}$$ zFySUFHB5U9Ym2vcYESkSbyAni`|D}#ZU}!sv#ZZ{ASRZhtfy&wud#2Rrdo;2pJVL; zD*7!mi{vAdM7B7&<36q8?8X!CDy-`I&7N30kza;3_{ zPmVbfJHKV_QQ@VgYUX&ISe33Bg4mJcgH;goqBJ{(PvdmJ63{yqM(MB|?qgj~i`S2j zdsro2eezZ1Fqp32luSFZhuJSK22~*Y;y+TbkweI|DRsFPUyZKd*#p`$-=iZ6Lfar3 zJH)Kl_OK^GLt?#V-HSOAP75?-tjYk^$Tn=(YO>FGHEP{XBuwOb&x4nNb@CZ-$z1{l zN_O;0SG*J$Dne4(u%xkH?t=);Q)IJRQU|TK*1S)$jo;BMu<+c8r|q70MVJ&BM$X&h zGn^@W;b%|P?BJ;{IxVh+Nh}&di)J@u1{l=_#5Z=vO7mDD?{qPGkwkt1lNGB{j|}(s z&lP;d1-qy7w*0EDR*r6{)d!2_P4ClXD!)JUWUfx)kBj(-r1q}X#rtB^WVi$90{)4Y z`nmpkV!I>hGqlsPzw%*a5?K9*USuQ-$K8qFIVLEMiNW1xq*911#2*p1PK!(}!R^X_ zyhX=OZ6qYI=vq0Xqa}iWN_Xv75zl~Cch@zQiGQ^R2<>yobil_2R=2iOjo9^Q1RDjAIvs%AHOZiFs=s9vpi`z^hlHGsBu0 zzToO>%v(4AE`$0H4#NGEGHQcH(DVDFOeP9ZSZMF1&rrm|2 z6hvdOVxX(F+P7?!vn;`227}in3G)wA4)0L$_~yVNJMm5$GB6el z

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 NuoW6XK{Y+~{{rUp!#Dr{ literal 0 HcmV?d00001 diff --git a/docs/app-modules/interchain-accounts/auth-modules.md b/docs/apps/interchain-accounts/auth-modules.md similarity index 100% rename from docs/app-modules/interchain-accounts/auth-modules.md rename to docs/apps/interchain-accounts/auth-modules.md diff --git a/docs/app-modules/interchain-accounts/integration.md b/docs/apps/interchain-accounts/integration.md similarity index 100% rename from docs/app-modules/interchain-accounts/integration.md rename to docs/apps/interchain-accounts/integration.md diff --git a/docs/app-modules/interchain-accounts/overview.md b/docs/apps/interchain-accounts/overview.md similarity index 100% rename from docs/app-modules/interchain-accounts/overview.md rename to docs/apps/interchain-accounts/overview.md diff --git a/docs/app-modules/interchain-accounts/parameters.md b/docs/apps/interchain-accounts/parameters.md similarity index 100% rename from docs/app-modules/interchain-accounts/parameters.md rename to docs/apps/interchain-accounts/parameters.md diff --git a/docs/app-modules/interchain-accounts/transactions.md b/docs/apps/interchain-accounts/transactions.md similarity index 100% rename from docs/app-modules/interchain-accounts/transactions.md rename to docs/apps/interchain-accounts/transactions.md diff --git a/docs/migrations/v2-to-v3.md b/docs/migrations/v2-to-v3.md index 9b7615974c0..fb47615d0f7 100644 --- a/docs/migrations/v2-to-v3.md +++ b/docs/migrations/v2-to-v3.md @@ -26,7 +26,7 @@ The ICS4Wrapper should be the IBC Channel Keeper unless ICS 20 is being connecte ### ICS27 ICS27 Interchain Accounts has been added as a supported IBC application of ibc-go. -Please see the [ICS27 documentation](../app-modules/interchain-accounts/overview.md) for more information. +Please see the [ICS27 documentation](../apps/interchain-accounts/overview.md) for more information. ### Upgrade Proposal From 55344184b3a5a5eb2ad2a96a7b0f715a210494f9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 17 Feb 2022 12:08:42 +0000 Subject: [PATCH 033/140] refactor: WriteAcknowledgement API (backport #882) (#943) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: WriteAcknowledgement API (#882) * refactor: WriteAcknowledgement takes exported.Acknowledgement instead of bytes * fix: adding check for empty byte string * chore: update changelog * fixing test case + adding migration docs * testing: Adding MockEmptyAcknowledgement to testing library * docs: fix version * test: add check for ack is nil (cherry picked from commit acbc9b61d10bf892528a392595782ac17aeeca30) # Conflicts: # CHANGELOG.md * fix changelog merge conflict * backport migration docs Co-authored-by: Sean King Co-authored-by: Colin Axnér <25233464+colin-axner@users.noreply.github.com> --- CHANGELOG.md | 1 + docs/migrations/v2-to-v3.md | 6 +++++ modules/core/04-channel/keeper/packet.go | 13 ++++++---- modules/core/04-channel/keeper/packet_test.go | 24 +++++++++++++------ modules/core/05-port/types/module.go | 2 +- modules/core/keeper/msg_server.go | 2 +- testing/endpoint.go | 2 +- testing/mock/ack.go | 23 ++++++++++++++++++ 8 files changed, 59 insertions(+), 14 deletions(-) create mode 100644 testing/mock/ack.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 486d58d0c54..04178d9e792 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (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 ### State Machine Breaking diff --git a/docs/migrations/v2-to-v3.md b/docs/migrations/v2-to-v3.md index fb47615d0f7..ec5bd89afd9 100644 --- a/docs/migrations/v2-to-v3.md +++ b/docs/migrations/v2-to-v3.md @@ -18,6 +18,12 @@ No genesis or in-place migrations are required when upgrading from v1 or v2 of i ## Chains +### IS04 - 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`. + + ### ICS20 The `transferkeeper.NewKeeper(...)` now takes in an ICS4Wrapper. diff --git a/modules/core/04-channel/keeper/packet.go b/modules/core/04-channel/keeper/packet.go index fd1322dcbbc..5879c9ecb08 100644 --- a/modules/core/04-channel/keeper/packet.go +++ b/modules/core/04-channel/keeper/packet.go @@ -312,7 +312,7 @@ func (k Keeper) WriteAcknowledgement( ctx sdk.Context, chanCap *capabilitytypes.Capability, packet exported.PacketI, - acknowledgement []byte, + acknowledgement exported.Acknowledgement, ) error { channel, found := k.GetChannel(ctx, packet.GetDestPort(), packet.GetDestChannel()) if !found { @@ -342,14 +342,19 @@ func (k Keeper) WriteAcknowledgement( return types.ErrAcknowledgementExists } - if len(acknowledgement) == 0 { + if acknowledgement == nil { + return sdkerrors.Wrap(types.ErrInvalidAcknowledgement, "acknowledgement cannot be nil") + } + + bz := acknowledgement.Acknowledgement() + if len(bz) == 0 { return sdkerrors.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(acknowledgement), + types.CommitAcknowledgement(bz), ) // log that a packet acknowledgement has been written @@ -362,7 +367,7 @@ func (k Keeper) WriteAcknowledgement( "dst_channel", packet.GetDestChannel(), ) - EmitWriteAcknowledgementEvent(ctx, packet, channel, acknowledgement) + EmitWriteAcknowledgementEvent(ctx, packet, channel, bz) return nil } diff --git a/modules/core/04-channel/keeper/packet_test.go b/modules/core/04-channel/keeper/packet_test.go index 69587080cf9..db6cce545c9 100644 --- a/modules/core/04-channel/keeper/packet_test.go +++ b/modules/core/04-channel/keeper/packet_test.go @@ -493,7 +493,7 @@ func (suite *KeeperTestSuite) TestRecvPacket() { func (suite *KeeperTestSuite) TestWriteAcknowledgement() { var ( path *ibctesting.Path - ack []byte + ack exported.Acknowledgement packet exported.PacketI channelCap *capabilitytypes.Capability ) @@ -504,7 +504,7 @@ func (suite *KeeperTestSuite) TestWriteAcknowledgement() { func() { suite.coordinator.Setup(path) packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) - ack = ibctesting.MockAcknowledgement + ack = ibcmock.MockAcknowledgement channelCap = suite.chainB.GetChannelCapability(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) }, true, @@ -513,13 +513,13 @@ func (suite *KeeperTestSuite) TestWriteAcknowledgement() { // use wrong channel naming suite.coordinator.Setup(path) packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, ibctesting.InvalidID, ibctesting.InvalidID, timeoutHeight, disabledTimeoutTimestamp) - ack = ibctesting.MockAcknowledgement + ack = ibcmock.MockAcknowledgement channelCap = suite.chainB.GetChannelCapability(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) }, false}, {"channel not open", func() { suite.coordinator.Setup(path) packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) - ack = ibctesting.MockAcknowledgement + ack = ibcmock.MockAcknowledgement err := path.EndpointB.SetChannelClosed() suite.Require().NoError(err) @@ -530,7 +530,7 @@ func (suite *KeeperTestSuite) TestWriteAcknowledgement() { func() { suite.coordinator.Setup(path) packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) - ack = ibctesting.MockAcknowledgement + ack = ibcmock.MockAcknowledgement channelCap = capabilitytypes.NewCapability(3) }, false, @@ -540,14 +540,24 @@ func (suite *KeeperTestSuite) TestWriteAcknowledgement() { func() { suite.coordinator.Setup(path) packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) - ack = ibctesting.MockAcknowledgement - suite.chainB.App.GetIBCKeeper().ChannelKeeper.SetPacketAcknowledgement(suite.chainB.GetContext(), packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), ack) + ack = ibcmock.MockAcknowledgement + suite.chainB.App.GetIBCKeeper().ChannelKeeper.SetPacketAcknowledgement(suite.chainB.GetContext(), packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), ack.Acknowledgement()) channelCap = suite.chainB.GetChannelCapability(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) }, false, }, { "empty acknowledgement", + func() { + suite.coordinator.Setup(path) + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) + ack = ibcmock.NewMockEmptyAcknowledgement() + channelCap = suite.chainB.GetChannelCapability(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + }, + false, + }, + { + "acknowledgement is nil", func() { suite.coordinator.Setup(path) packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) diff --git a/modules/core/05-port/types/module.go b/modules/core/05-port/types/module.go index 9c7442a9d76..dea418b7250 100644 --- a/modules/core/05-port/types/module.go +++ b/modules/core/05-port/types/module.go @@ -110,7 +110,7 @@ type ICS4Wrapper interface { ctx sdk.Context, chanCap *capabilitytypes.Capability, packet exported.PacketI, - ack []byte, + ack exported.Acknowledgement, ) error } diff --git a/modules/core/keeper/msg_server.go b/modules/core/keeper/msg_server.go index 9342c0ff069..b19041f636d 100644 --- a/modules/core/keeper/msg_server.go +++ b/modules/core/keeper/msg_server.go @@ -417,7 +417,7 @@ func (k Keeper) RecvPacket(goCtx context.Context, msg *channeltypes.MsgRecvPacke // NOTE: IBC applications modules may call the WriteAcknowledgement asynchronously if the // acknowledgement is nil. if ack != nil { - if err := k.ChannelKeeper.WriteAcknowledgement(ctx, cap, msg.Packet, ack.Acknowledgement()); err != nil { + if err := k.ChannelKeeper.WriteAcknowledgement(ctx, cap, msg.Packet, ack); err != nil { return nil, err } } diff --git a/testing/endpoint.go b/testing/endpoint.go index 39fe2b4408c..962ecf5ef35 100644 --- a/testing/endpoint.go +++ b/testing/endpoint.go @@ -411,7 +411,7 @@ func (endpoint *Endpoint) WriteAcknowledgement(ack exported.Acknowledgement, pac channelCap := endpoint.Chain.GetChannelCapability(packet.GetDestPort(), packet.GetDestChannel()) // no need to send message, acting as a handler - err := endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.WriteAcknowledgement(endpoint.Chain.GetContext(), channelCap, packet, ack.Acknowledgement()) + err := endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.WriteAcknowledgement(endpoint.Chain.GetContext(), channelCap, packet, ack) if err != nil { return err } diff --git a/testing/mock/ack.go b/testing/mock/ack.go new file mode 100644 index 00000000000..c25176a02da --- /dev/null +++ b/testing/mock/ack.go @@ -0,0 +1,23 @@ +package mock + +// MockEmptyAcknowledgement implements the exported.Acknowledgement interface and always returns an empty byte string as Response +type MockEmptyAcknowledgement struct { + Response []byte +} + +// NewMockEmptyAcknowledgement returns a new instance of MockEmptyAcknowledgement +func NewMockEmptyAcknowledgement() MockEmptyAcknowledgement { + return MockEmptyAcknowledgement{ + Response: []byte{}, + } +} + +// Success implements the Acknowledgement interface +func (ack MockEmptyAcknowledgement) Success() bool { + return true +} + +// Acknowledgement implements the Acknowledgement interface +func (ack MockEmptyAcknowledgement) Acknowledgement() []byte { + return []byte{} +} From 8c17224f1714477e9373735325ada1fb3ea7f69f Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Fri, 18 Feb 2022 09:46:50 +0100 Subject: [PATCH 034/140] add changelog entry for bump of SDK --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04178d9e792..6c9f0c55b35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,12 +39,13 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### 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 * (core) [\#709](https://github.com/cosmos/ibc-go/pull/709) Replace github.com/pkg/errors with stdlib errors ### API Breaking -* (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. +* (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. From 523c5960c60bac60317637f58a5e99514e86f9ce Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 22 Feb 2022 08:55:11 +0000 Subject: [PATCH 035/140] testing: adding multiple sender accounts for testing purposes (#935) (#959) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * testing: adding multiple sender accounts for testing puproses * fix genesis setup (#936) * Update testing/chain.go Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> * refactor: code hygiene * Update testing/chain.go Co-authored-by: Aditya * fix: setting totalySupply to empty * nit: CamelCase not UPPERCASE Co-authored-by: Aditya Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> (cherry picked from commit 20dd5cacc4cf983b5628b39908b59a90a4171e95) Co-authored-by: Sean King --- testing/app.go | 14 ++++------ testing/chain.go | 70 +++++++++++++++++++++++++++++++++--------------- 2 files changed, 54 insertions(+), 30 deletions(-) diff --git a/testing/app.go b/testing/app.go index 487e0569ba2..320bb0f00a7 100644 --- a/testing/app.go +++ b/testing/app.go @@ -91,22 +91,18 @@ func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs delegations = append(delegations, stakingtypes.NewDelegation(genAccs[0].GetAddress(), val.Address.Bytes(), sdk.OneDec())) } - // set validators and delegations - stakingGenesis := stakingtypes.NewGenesisState(stakingtypes.DefaultParams(), validators, delegations) - genesisState[stakingtypes.ModuleName] = app.AppCodec().MustMarshalJSON(stakingGenesis) - totalSupply := sdk.NewCoins() - for _, b := range balances { - // add genesis acc tokens and delegated tokens to total supply - totalSupply = totalSupply.Add(b.Coins.Add(sdk.NewCoin(sdk.DefaultBondDenom, bondAmt))...) - } // add bonded amount to bonded pool module account balances = append(balances, banktypes.Balance{ Address: authtypes.NewModuleAddress(stakingtypes.BondedPoolName).String(), - Coins: sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, bondAmt)}, + Coins: sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, bondAmt.Mul(sdk.NewInt(int64(len(valSet.Validators)))))}, }) + // set validators and delegations + stakingGenesis := stakingtypes.NewGenesisState(stakingtypes.DefaultParams(), validators, delegations) + genesisState[stakingtypes.ModuleName] = app.AppCodec().MustMarshalJSON(stakingGenesis) + // update total supply bankGenesis := banktypes.NewGenesisState(banktypes.DefaultGenesisState().Params, balances, totalSupply, []banktypes.Metadata{}) genesisState[banktypes.ModuleName] = app.AppCodec().MustMarshalJSON(bankGenesis) diff --git a/testing/chain.go b/testing/chain.go index 8dfe4b8fb27..86717501c6a 100644 --- a/testing/chain.go +++ b/testing/chain.go @@ -36,6 +36,15 @@ import ( "github.com/cosmos/ibc-go/v3/testing/simapp" ) +var ( + MaxAccounts = 10 +) + +type SenderAccount struct { + SenderPrivKey cryptotypes.PrivKey + SenderAccount authtypes.AccountI +} + // 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 @@ -59,6 +68,8 @@ type TestChain struct { // autogenerated sender private key SenderPrivKey cryptotypes.PrivKey SenderAccount authtypes.AccountI + + SenderAccounts []SenderAccount } // NewTestChain initializes a new TestChain instance with a single validator set using a @@ -84,18 +95,34 @@ func NewTestChain(t *testing.T, coord *Coordinator, chainID string) *TestChain { valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{validator}) signers := []tmtypes.PrivValidator{privVal} - // generate genesis account - senderPrivKey := secp256k1.GenPrivKey() - acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0) - amount, ok := sdk.NewIntFromString("10000000000000000000") - require.True(t, ok) + genAccs := []authtypes.GenesisAccount{} + genBals := []banktypes.Balance{} + senderAccs := []SenderAccount{} + + // generate genesis accounts + for i := 0; i < MaxAccounts; i++ { + senderPrivKey := secp256k1.GenPrivKey() + acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), uint64(i), 0) + amount, ok := sdk.NewIntFromString("10000000000000000000") + require.True(t, ok) + + balance := banktypes.Balance{ + Address: acc.GetAddress().String(), + Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, amount)), + } + + genAccs = append(genAccs, acc) + genBals = append(genBals, balance) + + senderAcc := SenderAccount{ + SenderAccount: acc, + SenderPrivKey: senderPrivKey, + } - balance := banktypes.Balance{ - Address: acc.GetAddress().String(), - Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, amount)), + senderAccs = append(senderAccs, senderAcc) } - app := SetupWithGenesisValSet(t, valSet, []authtypes.GenesisAccount{acc}, chainID, balance) + app := SetupWithGenesisValSet(t, valSet, genAccs, chainID, genBals...) // create current header and call begin block header := tmproto.Header{ @@ -108,18 +135,19 @@ func NewTestChain(t *testing.T, coord *Coordinator, chainID string) *TestChain { // create an account to send transactions from chain := &TestChain{ - t: t, - Coordinator: coord, - ChainID: chainID, - App: app, - CurrentHeader: header, - QueryServer: app.GetIBCKeeper(), - TxConfig: txConfig, - Codec: app.AppCodec(), - Vals: valSet, - Signers: signers, - SenderPrivKey: senderPrivKey, - SenderAccount: acc, + t: t, + Coordinator: coord, + ChainID: chainID, + App: app, + CurrentHeader: header, + QueryServer: app.GetIBCKeeper(), + TxConfig: txConfig, + Codec: app.AppCodec(), + Vals: valSet, + Signers: signers, + SenderPrivKey: senderAccs[0].SenderPrivKey, + SenderAccount: senderAccs[0].SenderAccount, + SenderAccounts: senderAccs, } coord.CommitBlock(chain) From b6040302c0c9a50691ad2179839cda02b5947cf4 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 22 Feb 2022 11:43:40 +0000 Subject: [PATCH 036/140] fix: classify client states without consensus states as expired (#941) (#969) ## Description closes: #850 --- 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. - [ ] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [ ] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit d48f576a0b2fe752790339f9e6f99b99d9fe2486) Co-authored-by: Tim Lind --- modules/core/02-client/keeper/grpc_query_test.go | 2 +- modules/light-clients/07-tendermint/types/client_state.go | 4 +++- .../light-clients/07-tendermint/types/client_state_test.go | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/core/02-client/keeper/grpc_query_test.go b/modules/core/02-client/keeper/grpc_query_test.go index f4fa3c59322..5e393c33a97 100644 --- a/modules/core/02-client/keeper/grpc_query_test.go +++ b/modules/core/02-client/keeper/grpc_query_test.go @@ -452,7 +452,7 @@ func (suite *KeeperTestSuite) TestQueryClientStatus() { ClientId: path.EndpointA.ClientID, } }, - true, exported.Unknown.String(), + true, exported.Expired.String(), }, { "Frozen client status", diff --git a/modules/light-clients/07-tendermint/types/client_state.go b/modules/light-clients/07-tendermint/types/client_state.go index a0430337d0b..51f826979fd 100644 --- a/modules/light-clients/07-tendermint/types/client_state.go +++ b/modules/light-clients/07-tendermint/types/client_state.go @@ -78,7 +78,9 @@ func (cs ClientState) Status( // get latest consensus state from clientStore to check for expiry consState, err := GetConsensusState(clientStore, cdc, cs.GetLatestHeight()) if err != nil { - return exported.Unknown + // 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()) { diff --git a/modules/light-clients/07-tendermint/types/client_state_test.go b/modules/light-clients/07-tendermint/types/client_state_test.go index b0434579b76..cf52d2996b5 100644 --- a/modules/light-clients/07-tendermint/types/client_state_test.go +++ b/modules/light-clients/07-tendermint/types/client_state_test.go @@ -47,10 +47,10 @@ func (suite *TendermintTestSuite) TestStatus() { clientState.FrozenHeight = clienttypes.NewHeight(0, 1) path.EndpointA.SetClientState(clientState) }, exported.Frozen}, - {"client status is unknown", func() { + {"client status without consensus state", func() { clientState.LatestHeight = clientState.LatestHeight.Increment().(clienttypes.Height) path.EndpointA.SetClientState(clientState) - }, exported.Unknown}, + }, exported.Expired}, {"client status is expired", func() { suite.coordinator.IncrementTimeBy(clientState.TrustingPeriod) }, exported.Expired}, From e93162c7bb2fe91131365edcacc58f7424720b28 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 22 Feb 2022 13:35:48 +0100 Subject: [PATCH 037/140] Create test chain with multiple validators (#942) (#960) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * testing: adding multiple sender accounts for testing puproses * fix genesis setup (#936) * Update testing/chain.go Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> * refactor: code hygiene * Update testing/chain.go Co-authored-by: Aditya * multi validator commit taken from @saione * add function to pass custom valset * add changelog Co-authored-by: Sean King Co-authored-by: Sean King Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> (cherry picked from commit 98f4d3af3d9ea8f345c03031deec3162d21d5c45) # Conflicts: # testing/chain.go Co-authored-by: Aditya --- CHANGELOG.md | 1 + testing/chain.go | 54 +++++++++++++++++++++++++++++++++++------------- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c9f0c55b35..90f8e4b0af4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements +* (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. diff --git a/testing/chain.go b/testing/chain.go index 86717501c6a..89e5b5cb308 100644 --- a/testing/chain.go +++ b/testing/chain.go @@ -72,9 +72,9 @@ type TestChain struct { SenderAccounts []SenderAccount } -// NewTestChain initializes a new TestChain instance with a single validator set using a -// generated secp256k1 Tendermint private key. It also creates a sender BaseAccount to be used for -// delivering transactions. +// 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. @@ -84,17 +84,11 @@ type TestChain struct { // // NOTE: to use a custom sender privkey and account for testing purposes, replace and modify this // constructor function. -func NewTestChain(t *testing.T, coord *Coordinator, chainID string) *TestChain { - // generate validator private/public key - privVal := mock.NewPV() - pubKey, err := privVal.GetPubKey() - require.NoError(t, err) - - // create validator set with single validator - validator := tmtypes.NewValidator(pubKey, 1) - valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{validator}) - signers := []tmtypes.PrivValidator{privVal} - +// +// CONTRACT: Validator and signer array must be provided in the order expected by Tendermint. +// i.e. sorted first by power and then lexicographically by address. +func NewTestChainWithValSet(t *testing.T, coord *Coordinator, chainID string, valSet *tmtypes.ValidatorSet, signers []tmtypes.PrivValidator) *TestChain { + genAccs := []authtypes.GenesisAccount{} genBals := []banktypes.Balance{} senderAccs := []SenderAccount{} @@ -155,6 +149,38 @@ func NewTestChain(t *testing.T, coord *Coordinator, chainID string) *TestChain { return chain } +// 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 { + // generate validators private/public key + var ( + validatorsPerChain = 4 + validators []*tmtypes.Validator + signersByAddress = make(map[string]tmtypes.PrivValidator, validatorsPerChain) + ) + + for i := 0; i < validatorsPerChain; i++ { + privVal := mock.NewPV() + pubKey, err := privVal.GetPubKey() + require.NoError(t, err) + validators = append(validators, tmtypes.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 := tmtypes.NewValidatorSet(validators) + + // create signers indexed by the valSet validators's order + signers := []tmtypes.PrivValidator{} + for _, val := range valSet.Validators { + signers = append(signers, signersByAddress[val.PubKey.Address().String()]) + } + + return NewTestChainWithValSet(t, coord, chainID, valSet, signers) +} + // GetContext returns the current context for the application. func (chain *TestChain) GetContext() sdk.Context { return chain.App.GetBaseApp().NewContext(false, chain.CurrentHeader) From 0b18651ba937ef6a9e93bab7c6176818248f4c44 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 23 Feb 2022 14:56:15 +0000 Subject: [PATCH 038/140] chore: update migration docs (#985) (#986) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: update migration docs * Update docs/migrations/v2-to-v3.md Co-authored-by: Damian Nolan Co-authored-by: Damian Nolan (cherry picked from commit ef34765e7010ec5f526a44587de4be0e2afbde12) Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> --- docs/migrations/v2-to-v3.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/migrations/v2-to-v3.md b/docs/migrations/v2-to-v3.md index ec5bd89afd9..89336d69b10 100644 --- a/docs/migrations/v2-to-v3.md +++ b/docs/migrations/v2-to-v3.md @@ -61,6 +61,32 @@ app.UpgradeKeeper.SetUpgradeHandler("v3", The host and controller submodule params only need to be set if you integrate those submodules. For example, if a chain chooses not to integrate a controller submodule, it does not need to set the controller params. +### 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) +``` + ## IBC Apps @@ -97,6 +123,10 @@ As apart of this release, the mock module now supports middleware testing. Pleas Please review the [mock](../../testing/mock/ibc_module.go) and [transfer](../../modules/apps/transfer/ibc_module.go) modules as examples. Additionally, [simapp](../../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. From 3c33f776627b62b6411c466e6040636bbd6a2aa4 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 1 Mar 2022 09:48:59 +0100 Subject: [PATCH 039/140] bug: support base denoms with slashes (#978) (#1019) * bug: support base denoms with slashes * add changelog entry Co-authored-by: Carlos Rodriguez (cherry picked from commit 4545154cc60db44a26dadec2e2b2422bd138f5d1) Co-authored-by: Carlos Rodriguez --- CHANGELOG.md | 1 + modules/apps/transfer/types/msgs_test.go | 2 +- modules/apps/transfer/types/trace.go | 18 +++++++++--------- modules/apps/transfer/types/trace_test.go | 5 +++-- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90f8e4b0af4..4508636a8c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### 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 ## [v2.0.2](https://github.com/cosmos/ibc-go/releases/tag/v2.0.2) - 2021-12-15 diff --git a/modules/apps/transfer/types/msgs_test.go b/modules/apps/transfer/types/msgs_test.go index 5cf074a6d8d..00570ac15ed 100644 --- a/modules/apps/transfer/types/msgs_test.go +++ b/modules/apps/transfer/types/msgs_test.go @@ -32,7 +32,7 @@ var ( coin = sdk.NewCoin("atom", sdk.NewInt(100)) ibcCoin = sdk.NewCoin("ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2", sdk.NewInt(100)) - invalidIBCCoin = sdk.NewCoin("notibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2", sdk.NewInt(100)) + invalidIBCCoin = sdk.NewCoin("ibc/7F1D3FCF4AE79E1554", sdk.NewInt(100)) invalidDenomCoin = sdk.Coin{Denom: "0atom", Amount: sdk.NewInt(100)} zeroCoin = sdk.Coin{Denom: "atoms", Amount: sdk.NewInt(0)} diff --git a/modules/apps/transfer/types/trace.go b/modules/apps/transfer/types/trace.go index 6bbbbadfc23..303ddd3769e 100644 --- a/modules/apps/transfer/types/trace.go +++ b/modules/apps/transfer/types/trace.go @@ -162,7 +162,7 @@ func ValidatePrefixedDenom(denom string) error { // ValidateIBCDenom validates that the given denomination is either: // -// - A valid base denomination (eg: 'uatom') +// - 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 { @@ -172,17 +172,17 @@ func ValidateIBCDenom(denom string) error { denomSplit := strings.SplitN(denom, "/", 2) switch { - case strings.TrimSpace(denom) == "", - len(denomSplit) == 1 && denomSplit[0] == DenomPrefix, - len(denomSplit) == 2 && (denomSplit[0] != DenomPrefix || strings.TrimSpace(denomSplit[1]) == ""): + case denom == DenomPrefix: return sdkerrors.Wrapf(ErrInvalidDenomForTransfer, "denomination should be prefixed with the format 'ibc/{hash(trace + \"/\" + %s)}'", denom) - case denomSplit[0] == denom && strings.TrimSpace(denom) != "": - return nil - } + case len(denomSplit) == 2 && denomSplit[0] == DenomPrefix: + if strings.TrimSpace(denomSplit[1]) == "" { + return sdkerrors.Wrapf(ErrInvalidDenomForTransfer, "denomination should be prefixed with the format 'ibc/{hash(trace + \"/\" + %s)}'", denom) + } - if _, err := ParseHexHash(denomSplit[1]); err != nil { - return sdkerrors.Wrapf(err, "invalid denom trace hash %s", denomSplit[1]) + if _, err := ParseHexHash(denomSplit[1]); err != nil { + return sdkerrors.Wrapf(err, "invalid denom trace hash %s", denomSplit[1]) + } } return nil diff --git a/modules/apps/transfer/types/trace_test.go b/modules/apps/transfer/types/trace_test.go index f0868d5680e..e35fd33317b 100644 --- a/modules/apps/transfer/types/trace_test.go +++ b/modules/apps/transfer/types/trace_test.go @@ -131,11 +131,12 @@ func TestValidateIBCDenom(t *testing.T) { }{ {"denom with trace hash", "ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2", false}, {"base denom", "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}, - {"invalid prefixed denom", "transfer/channelToA/uatom", true}, {"denom 'ibc'", "ibc", true}, {"denom 'ibc/'", "ibc/", true}, - {"invald prefix", "notibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2", true}, {"invald hash", "ibc/!@#$!@#", true}, } From 171498a5fc612bcb66c2dceea21c890e9c7ece4b Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 1 Mar 2022 09:24:29 +0000 Subject: [PATCH 040/140] ibctesting: make `testing.T` public (#1020) (#1030) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit 6d6888b9c5cffc6b9c0f09ec5f77a8932d0b7e86) Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> Co-authored-by: Damian Nolan --- testing/chain.go | 41 +++++++++++++++++++---------------------- testing/coordinator.go | 26 +++++++++++++------------- testing/endpoint.go | 31 ++++++++++++++----------------- 3 files changed, 46 insertions(+), 52 deletions(-) diff --git a/testing/chain.go b/testing/chain.go index 89e5b5cb308..4199595c1b9 100644 --- a/testing/chain.go +++ b/testing/chain.go @@ -36,9 +36,7 @@ import ( "github.com/cosmos/ibc-go/v3/testing/simapp" ) -var ( - MaxAccounts = 10 -) +var MaxAccounts = 10 type SenderAccount struct { SenderPrivKey cryptotypes.PrivKey @@ -51,7 +49,7 @@ type SenderAccount struct { // 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 { - t *testing.T + *testing.T Coordinator *Coordinator App TestingApp @@ -88,7 +86,6 @@ type TestChain struct { // CONTRACT: Validator and signer array must be provided in the order expected by Tendermint. // i.e. sorted first by power and then lexicographically by address. func NewTestChainWithValSet(t *testing.T, coord *Coordinator, chainID string, valSet *tmtypes.ValidatorSet, signers []tmtypes.PrivValidator) *TestChain { - genAccs := []authtypes.GenesisAccount{} genBals := []banktypes.Balance{} senderAccs := []SenderAccount{} @@ -129,7 +126,7 @@ func NewTestChainWithValSet(t *testing.T, coord *Coordinator, chainID string, va // create an account to send transactions from chain := &TestChain{ - t: t, + T: t, Coordinator: coord, ChainID: chainID, App: app, @@ -191,7 +188,7 @@ func (chain *TestChain) GetContext() sdk.Context { // their own SimApp. func (chain *TestChain) GetSimApp() *simapp.SimApp { app, ok := chain.App.(*simapp.SimApp) - require.True(chain.t, ok) + require.True(chain.T, ok) return app } @@ -213,10 +210,10 @@ func (chain *TestChain) QueryProofAtHeight(key []byte, height int64) ([]byte, cl }) merkleProof, err := commitmenttypes.ConvertProofs(res.ProofOps) - require.NoError(chain.t, err) + require.NoError(chain.T, err) proof, err := chain.App.AppCodec().Marshal(&merkleProof) - require.NoError(chain.t, err) + require.NoError(chain.T, err) revision := clienttypes.ParseChainID(chain.ChainID) @@ -237,10 +234,10 @@ func (chain *TestChain) QueryUpgradeProof(key []byte, height uint64) ([]byte, cl }) merkleProof, err := commitmenttypes.ConvertProofs(res.ProofOps) - require.NoError(chain.t, err) + require.NoError(chain.T, err) proof, err := chain.App.AppCodec().Marshal(&merkleProof) - require.NoError(chain.t, err) + require.NoError(chain.T, err) revision := clienttypes.ParseChainID(chain.ChainID) @@ -300,7 +297,7 @@ func (chain *TestChain) SendMsgs(msgs ...sdk.Msg) (*sdk.Result, error) { chain.Coordinator.UpdateTimeForChain(chain) _, r, err := simapp.SignAndDeliver( - chain.t, + chain.T, chain.TxConfig, chain.App.GetBaseApp(), chain.GetContext().BlockHeader(), @@ -329,7 +326,7 @@ func (chain *TestChain) SendMsgs(msgs ...sdk.Msg) (*sdk.Result, error) { // 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.t, found) + require.True(chain.T, found) return clientState } @@ -361,7 +358,7 @@ func (chain *TestChain) GetValsAtHeight(height int64) (*tmtypes.ValidatorSet, bo // acknowledgement does not exist then testing will fail. func (chain *TestChain) GetAcknowledgement(packet exported.PacketI) []byte { ack, found := chain.App.GetIBCKeeper().ChannelKeeper.GetPacketAcknowledgement(chain.GetContext(), packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) - require.True(chain.t, found) + require.True(chain.T, found) return ack } @@ -436,7 +433,7 @@ func (chain *TestChain) CreateTMClientHeader(chainID string, blockHeight int64, valSet *tmproto.ValidatorSet trustedVals *tmproto.ValidatorSet ) - require.NotNil(chain.t, tmValSet) + require.NotNil(chain.T, tmValSet) vsetHash := tmValSet.Hash() @@ -461,7 +458,7 @@ func (chain *TestChain) CreateTMClientHeader(chainID string, blockHeight int64, voteSet := tmtypes.NewVoteSet(chainID, blockHeight, 1, tmproto.PrecommitType, tmValSet) commit, err := tmtypes.MakeCommit(blockID, blockHeight, 1, voteSet, signers, timestamp) - require.NoError(chain.t, err) + require.NoError(chain.T, err) signedHeader := &tmproto.SignedHeader{ Header: tmHeader.ToProto(), @@ -533,11 +530,11 @@ func (chain *TestChain) CreatePortCapability(scopedKeeper capabilitykeeper.Scope if !ok { // create capability using the IBC capability keeper cap, err := chain.App.GetScopedIBCKeeper().NewCapability(chain.GetContext(), host.PortPath(portID)) - require.NoError(chain.t, err) + require.NoError(chain.T, err) // claim capability using the scopedKeeper err = scopedKeeper.ClaimCapability(chain.GetContext(), cap, host.PortPath(portID)) - require.NoError(chain.t, err) + require.NoError(chain.T, err) } chain.App.Commit() @@ -549,7 +546,7 @@ func (chain *TestChain) CreatePortCapability(scopedKeeper capabilitykeeper.Scope // exist, otherwise testing will fail. func (chain *TestChain) GetPortCapability(portID string) *capabilitytypes.Capability { cap, ok := chain.App.GetScopedIBCKeeper().GetCapability(chain.GetContext(), host.PortPath(portID)) - require.True(chain.t, ok) + require.True(chain.T, ok) return cap } @@ -563,9 +560,9 @@ func (chain *TestChain) CreateChannelCapability(scopedKeeper capabilitykeeper.Sc _, ok := chain.App.GetScopedIBCKeeper().GetCapability(chain.GetContext(), capName) if !ok { cap, err := chain.App.GetScopedIBCKeeper().NewCapability(chain.GetContext(), capName) - require.NoError(chain.t, err) + require.NoError(chain.T, err) err = scopedKeeper.ClaimCapability(chain.GetContext(), cap, capName) - require.NoError(chain.t, err) + require.NoError(chain.T, err) } chain.App.Commit() @@ -577,7 +574,7 @@ func (chain *TestChain) CreateChannelCapability(scopedKeeper capabilitykeeper.Sc // The capability must exist, otherwise testing will fail. func (chain *TestChain) GetChannelCapability(portID, channelID string) *capabilitytypes.Capability { cap, ok := chain.App.GetScopedIBCKeeper().GetCapability(chain.GetContext(), host.ChannelCapabilityPath(portID, channelID)) - require.True(chain.t, ok) + require.True(chain.T, ok) return cap } diff --git a/testing/coordinator.go b/testing/coordinator.go index be308c790d5..5a7a91650fc 100644 --- a/testing/coordinator.go +++ b/testing/coordinator.go @@ -19,7 +19,7 @@ var ( // Coordinator is a testing struct which contains N TestChain's. It handles keeping all chains // in sync with regards to time. type Coordinator struct { - t *testing.T + *testing.T CurrentTime time.Time Chains map[string]*TestChain @@ -29,7 +29,7 @@ type Coordinator struct { func NewCoordinator(t *testing.T, n int) *Coordinator { chains := make(map[string]*TestChain) coord := &Coordinator{ - t: t, + T: t, CurrentTime: globalStartTime, } @@ -84,10 +84,10 @@ func (coord *Coordinator) Setup(path *Path) { // caller does not anticipate any errors. func (coord *Coordinator) SetupClients(path *Path) { err := path.EndpointA.CreateClient() - require.NoError(coord.t, err) + require.NoError(coord.T, err) err = path.EndpointB.CreateClient() - require.NoError(coord.t, err) + require.NoError(coord.T, err) } // SetupClientConnections is a helper function to create clients and the appropriate @@ -105,16 +105,16 @@ func (coord *Coordinator) SetupConnections(path *Path) { // successfully opened otherwise testing will fail. func (coord *Coordinator) CreateConnections(path *Path) { err := path.EndpointA.ConnOpenInit() - require.NoError(coord.t, err) + require.NoError(coord.T, err) err = path.EndpointB.ConnOpenTry() - require.NoError(coord.t, err) + require.NoError(coord.T, err) err = path.EndpointA.ConnOpenAck() - require.NoError(coord.t, err) + require.NoError(coord.T, err) err = path.EndpointB.ConnOpenConfirm() - require.NoError(coord.t, err) + require.NoError(coord.T, err) // ensure counterparty is up to date path.EndpointA.UpdateClient() @@ -146,16 +146,16 @@ func (coord *Coordinator) CreateTransferChannels(path *Path) { // opened otherwise testing will fail. func (coord *Coordinator) CreateChannels(path *Path) { err := path.EndpointA.ChanOpenInit() - require.NoError(coord.t, err) + require.NoError(coord.T, err) err = path.EndpointB.ChanOpenTry() - require.NoError(coord.t, err) + require.NoError(coord.T, err) err = path.EndpointA.ChanOpenAck() - require.NoError(coord.t, err) + require.NoError(coord.T, err) err = path.EndpointB.ChanOpenConfirm() - require.NoError(coord.t, err) + require.NoError(coord.T, err) // ensure counterparty is up to date path.EndpointA.UpdateClient() @@ -165,7 +165,7 @@ func (coord *Coordinator) CreateChannels(path *Path) { // 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)) + require.True(coord.T, found, fmt.Sprintf("%s chain does not exist", chainID)) return chain } diff --git a/testing/endpoint.go b/testing/endpoint.go index 962ecf5ef35..5d496494afc 100644 --- a/testing/endpoint.go +++ b/testing/endpoint.go @@ -88,7 +88,7 @@ func (endpoint *Endpoint) CreateClient() (err error) { switch endpoint.ClientConfig.GetClientType() { case exported.Tendermint: tmConfig, ok := endpoint.ClientConfig.(*TendermintConfig) - require.True(endpoint.Chain.t, ok) + require.True(endpoint.Chain.T, ok) height := endpoint.Counterparty.Chain.LastHeader.GetHeight().(clienttypes.Height) clientState = ibctmtypes.NewClientState( @@ -98,7 +98,7 @@ func (endpoint *Endpoint) CreateClient() (err error) { consensusState = endpoint.Counterparty.Chain.LastHeader.ConsensusState() case exported.Solomachine: // TODO - // solo := NewSolomachine(chain.t, endpoint.Chain.Codec, clientID, "", 1) + // solo := NewSolomachine(Chain.T, endpoint.Chain.Codec, clientID, "", 1) // clientState = solo.ClientState() // consensusState = solo.ConsensusState() @@ -113,7 +113,7 @@ func (endpoint *Endpoint) CreateClient() (err error) { msg, err := clienttypes.NewMsgCreateClient( clientState, consensusState, endpoint.Chain.SenderAccount.GetAddress().String(), ) - require.NoError(endpoint.Chain.t, err) + require.NoError(endpoint.Chain.T, err) res, err := endpoint.Chain.SendMsgs(msg) if err != nil { @@ -121,7 +121,7 @@ func (endpoint *Endpoint) CreateClient() (err error) { } endpoint.ClientID, err = ParseClientIDFromEvents(res.GetEvents()) - require.NoError(endpoint.Chain.t, err) + require.NoError(endpoint.Chain.T, err) return nil } @@ -131,9 +131,7 @@ func (endpoint *Endpoint) UpdateClient() (err error) { // ensure counterparty has committed state endpoint.Chain.Coordinator.CommitBlock(endpoint.Counterparty.Chain) - var ( - header exported.Header - ) + var header exported.Header switch endpoint.ClientConfig.GetClientType() { case exported.Tendermint: @@ -151,10 +149,9 @@ func (endpoint *Endpoint) UpdateClient() (err error) { endpoint.ClientID, header, endpoint.Chain.SenderAccount.GetAddress().String(), ) - require.NoError(endpoint.Chain.t, err) + require.NoError(endpoint.Chain.T, err) return endpoint.Chain.sendMsgs(msg) - } // ConnOpenInit will construct and execute a MsgConnectionOpenInit on the associated endpoint. @@ -171,7 +168,7 @@ func (endpoint *Endpoint) ConnOpenInit() error { } endpoint.ConnectionID, err = ParseConnectionIDFromEvents(res.GetEvents()) - require.NoError(endpoint.Chain.t, err) + require.NoError(endpoint.Chain.T, err) return nil } @@ -197,7 +194,7 @@ func (endpoint *Endpoint) ConnOpenTry() error { if endpoint.ConnectionID == "" { endpoint.ConnectionID, err = ParseConnectionIDFromEvents(res.GetEvents()) - require.NoError(endpoint.Chain.t, err) + require.NoError(endpoint.Chain.T, err) } return nil @@ -277,7 +274,7 @@ func (endpoint *Endpoint) ChanOpenInit() error { } endpoint.ChannelID, err = ParseChannelIDFromEvents(res.GetEvents()) - require.NoError(endpoint.Chain.t, err) + require.NoError(endpoint.Chain.T, err) return nil } @@ -303,7 +300,7 @@ func (endpoint *Endpoint) ChanOpenTry() error { if endpoint.ChannelID == "" { endpoint.ChannelID, err = ParseChannelIDFromEvents(res.GetEvents()) - require.NoError(endpoint.Chain.t, err) + require.NoError(endpoint.Chain.T, err) } // update version to selected app version @@ -449,7 +446,7 @@ func (endpoint *Endpoint) TimeoutPacket(packet channeltypes.Packet) error { proof, proofHeight := endpoint.Counterparty.QueryProof(packetKey) nextSeqRecv, found := endpoint.Counterparty.Chain.App.GetIBCKeeper().ChannelKeeper.GetNextSequenceRecv(endpoint.Counterparty.Chain.GetContext(), endpoint.ChannelConfig.PortID, endpoint.ChannelID) - require.True(endpoint.Chain.t, found) + require.True(endpoint.Chain.T, found) timeoutMsg := channeltypes.NewMsgTimeout( packet, nextSeqRecv, @@ -486,7 +483,7 @@ func (endpoint *Endpoint) SetClientState(clientState exported.ClientState) { // 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.t, found) + require.True(endpoint.Chain.T, found) return consensusState } @@ -500,7 +497,7 @@ func (endpoint *Endpoint) SetConsensusState(consensusState exported.ConsensusSta // 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.t, found) + require.True(endpoint.Chain.T, found) return connection } @@ -514,7 +511,7 @@ func (endpoint *Endpoint) SetConnection(connection connectiontypes.ConnectionEnd // 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.t, found) + require.True(endpoint.Chain.T, found) return channel } From 4af1e624bab1b6d17eaff0d6157505a17da9e0c8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 1 Mar 2022 11:04:10 +0100 Subject: [PATCH 041/140] upgrade ics23 to v0.7 (#948) (#1028) * upgrade ics23 to v0.7-rc * add changelog entry * update ics23 to final 0.7 Co-authored-by: Carlos Rodriguez (cherry picked from commit 9d8be7cbab5cb1987449ff92fa2376c1ca44ac30) Co-authored-by: Carlos Rodriguez --- CHANGELOG.md | 1 + go.mod | 2 +- go.sum | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4508636a8c3..16334bd3c98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * [\#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 diff --git a/go.mod b/go.mod index 639c5607ced..eb9dc1a9548 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alp require ( github.com/armon/go-metrics v0.3.10 - github.com/confio/ics23/go v0.6.6 + github.com/confio/ics23/go v0.7.0 github.com/cosmos/cosmos-sdk v0.45.1 github.com/gogo/protobuf v1.3.3 github.com/golang/protobuf v1.5.2 diff --git a/go.sum b/go.sum index 25a2e5dd4c4..9caa6a21997 100644 --- a/go.sum +++ b/go.sum @@ -184,8 +184,9 @@ github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:z github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coinbase/rosetta-sdk-go v0.7.0 h1:lmTO/JEpCvZgpbkOITL95rA80CPKb5CtMzLaqF2mCNg= github.com/coinbase/rosetta-sdk-go v0.7.0/go.mod h1:7nD3oBPIiHqhRprqvMgPoGxe/nyq3yftRmpsy29coWE= -github.com/confio/ics23/go v0.6.6 h1:pkOy18YxxJ/r0XFDCnrl4Bjv6h4LkBSpLS6F38mrKL8= github.com/confio/ics23/go v0.6.6/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= +github.com/confio/ics23/go v0.7.0 h1:00d2kukk7sPoHWL4zZBZwzxnpA2pec1NPdwbSokJ5w8= +github.com/confio/ics23/go v0.7.0/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 h1:NmTXa/uVnDyp0TY5MKi197+3HWcnYWfnHGyaFthlnGw= github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= From dcb52470c56b0110cb00a42e8198557a27602ffa Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 2 Mar 2022 10:22:17 +0100 Subject: [PATCH 042/140] feat: Add a function to initialize the ICS27 module via an upgrade proposal (#1037) (#1040) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description closes: #1034 --- 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. - [ ] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [ ] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit 147e0f1954ede691bb977ff185b9d0500ec4bd0a) Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> --- CHANGELOG.md | 1 + docs/migrations/v2-to-v3.md | 25 ++++--- modules/apps/27-interchain-accounts/module.go | 13 ++++ .../27-interchain-accounts/module_test.go | 74 +++++++++++++++++++ testing/simapp/app.go | 6 ++ 5 files changed, 110 insertions(+), 9 deletions(-) create mode 100644 modules/apps/27-interchain-accounts/module_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 16334bd3c98..cc976fd96cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements +* (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. diff --git a/docs/migrations/v2-to-v3.md b/docs/migrations/v2-to-v3.md index 89336d69b10..1a9220abcbe 100644 --- a/docs/migrations/v2-to-v3.md +++ b/docs/migrations/v2-to-v3.md @@ -39,17 +39,24 @@ Please see the [ICS27 documentation](../apps/interchain-accounts/overview.md) fo 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, vm module.VersionMap) (module.VersionMap, error) { - // set ICS27 Host submodule params - app.ICAHostKeeper.SetParams(ctx, icahosttypes.Params{ - HostEnabled: true, - AllowMessages: []string{"/cosmos.bank.v1beta1.MsgSend", ...], - }) + 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() - // set ICS27 Controller submodule params - app.ICAControllerKeeper.SetParams(ctx, icacontrollertypes.Params{ + + // 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) ... diff --git a/modules/apps/27-interchain-accounts/module.go b/modules/apps/27-interchain-accounts/module.go index b11c611e0c9..969c07caf9d 100644 --- a/modules/apps/27-interchain-accounts/module.go +++ b/modules/apps/27-interchain-accounts/module.go @@ -24,6 +24,7 @@ import ( hosttypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/host/types" "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/types" porttypes "github.com/cosmos/ibc-go/v3/modules/core/05-port/types" + ibchost "github.com/cosmos/ibc-go/v3/modules/core/24-host" ) var ( @@ -101,6 +102,18 @@ func NewAppModule(controllerKeeper *controllerkeeper.Keeper, hostKeeper *hostkee } } +// InitModule will initialize the interchain accounts moudule. It should only be +// called once and as an alternative to InitGenesis. +func (am AppModule) InitModule(ctx sdk.Context, controllerParams controllertypes.Params, hostParams hosttypes.Params) { + am.controllerKeeper.SetParams(ctx, controllerParams) + am.hostKeeper.SetParams(ctx, hostParams) + + cap := am.hostKeeper.BindPort(ctx, types.PortID) + if err := am.hostKeeper.ClaimCapability(ctx, cap, ibchost.PortPath(types.PortID)); err != nil { + panic(fmt.Sprintf("could not claim port capability: %v", err)) + } +} + // RegisterInvariants implements the AppModule interface func (AppModule) RegisterInvariants(ir sdk.InvariantRegistry) { } 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 00000000000..de5f51ae921 --- /dev/null +++ b/modules/apps/27-interchain-accounts/module_test.go @@ -0,0 +1,74 @@ +package ica_test + +import ( + "testing" + + "github.com/stretchr/testify/suite" + "github.com/tendermint/tendermint/libs/log" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + dbm "github.com/tendermint/tm-db" + + ica "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts" + controllertypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/controller/types" + hosttypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/host/types" + "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/types" + ibctesting "github.com/cosmos/ibc-go/v3/testing" + "github.com/cosmos/ibc-go/v3/testing/simapp" +) + +type InterchainAccountsTestSuite struct { + suite.Suite + + coordinator *ibctesting.Coordinator +} + +func TestICATestSuite(t *testing.T) { + suite.Run(t, new(InterchainAccountsTestSuite)) +} + +func (suite *InterchainAccountsTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) +} + +func (suite *InterchainAccountsTestSuite) TestInitModule() { + app := simapp.NewSimApp(log.NewNopLogger(), dbm.NewMemDB(), nil, true, map[int64]bool{}, simapp.DefaultNodeHome, 5, simapp.MakeTestEncodingConfig(), simapp.EmptyAppOptions{}) + icamodule, ok := app.GetModuleManager().Modules[types.ModuleName].(ica.AppModule) + suite.Require().True(ok) + + header := tmproto.Header{ + ChainID: "testchain", + Height: 1, + Time: suite.coordinator.CurrentTime.UTC(), + } + + ctx := app.GetBaseApp().NewContext(true, header) + + // ensure params are not set + suite.Require().Panics(func() { + app.ICAControllerKeeper.GetParams(ctx) + }) + suite.Require().Panics(func() { + app.ICAHostKeeper.GetParams(ctx) + }) + + controllerParams := controllertypes.DefaultParams() + controllerParams.ControllerEnabled = true + + hostParams := hosttypes.DefaultParams() + expAllowMessages := []string{"sdk.Msg"} + hostParams.HostEnabled = true + hostParams.AllowMessages = expAllowMessages + + suite.Require().False(app.IBCKeeper.PortKeeper.IsBound(ctx, types.PortID)) + + icamodule.InitModule(ctx, controllerParams, hostParams) + + controllerParams = app.ICAControllerKeeper.GetParams(ctx) + suite.Require().True(controllerParams.ControllerEnabled) + + hostParams = app.ICAHostKeeper.GetParams(ctx) + suite.Require().True(hostParams.HostEnabled) + suite.Require().Equal(expAllowMessages, hostParams.AllowMessages) + + suite.Require().True(app.IBCKeeper.PortKeeper.IsBound(ctx, types.PortID)) +} diff --git a/testing/simapp/app.go b/testing/simapp/app.go index a2b78e7d260..15add30faaf 100644 --- a/testing/simapp/app.go +++ b/testing/simapp/app.go @@ -571,6 +571,12 @@ func (app *SimApp) ModuleAccountAddrs() map[string]bool { return modAccAddrs } +// GetModuleManager returns the app module manager +// NOTE: used for testing purposes +func (app *SimApp) GetModuleManager() *module.Manager { + return app.mm +} + // LegacyAmino returns SimApp's amino codec. // // NOTE: This is solely to be used for testing purposes as it may be desirable From 11c018f678cd56d8312a29ac94c60f2df943f76f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 2 Mar 2022 12:19:01 +0000 Subject: [PATCH 043/140] fix package import (#1007) (#1036) (cherry picked from commit f994d1e7aefa653c18b87f58099a7805b83946ef) Co-authored-by: Carlos Rodriguez Co-authored-by: Damian Nolan --- modules/apps/transfer/types/ack_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/apps/transfer/types/ack_test.go b/modules/apps/transfer/types/ack_test.go index bc4e2d07afc..4f4c3a874d7 100644 --- a/modules/apps/transfer/types/ack_test.go +++ b/modules/apps/transfer/types/ack_test.go @@ -9,7 +9,7 @@ import ( tmprotostate "github.com/tendermint/tendermint/proto/tendermint/state" tmstate "github.com/tendermint/tendermint/state" - "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/host/types" + "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" ibctesting "github.com/cosmos/ibc-go/v3/testing" ) From 571ba40feff1b28cfdb94a547d6081790491fb13 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 3 Mar 2022 12:22:42 +0100 Subject: [PATCH 044/140] docs: add missing args to NewKeeper in integration docs (#1038) (#1054) ## Description This add some missing arguments to `ibckeeper.NewKeeper` and `ibctransferkeeper.NewKeeper` in integration docs --- 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. - [x] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [x] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [ ] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit f71a50585375b2fc002699dee80b71a682bb1bd0) Co-authored-by: daeMOn --- docs/ibc/integration.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/ibc/integration.md b/docs/ibc/integration.md index f823ffe07e4..09c1d2d2de9 100644 --- a/docs/ibc/integration.md +++ b/docs/ibc/integration.md @@ -92,13 +92,13 @@ func NewApp(...args) *App { // Create IBC Keeper app.IBCKeeper = ibckeeper.NewKeeper( - appCodec, keys[ibchost.StoreKey], app.StakingKeeper, scopedIBCKeeper, + 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.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + 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) From e43079eb62c71335cc32a21a3cf7b23db6c65882 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 3 Mar 2022 16:45:44 +0100 Subject: [PATCH 045/140] chore: fix mispelled words (#991) (#1072) (cherry picked from commit 556cc01ad9516d8455e3abc06e2fc7f6d7533bff) Co-authored-by: Sean King --- modules/apps/transfer/ibc_module.go | 2 +- modules/core/04-channel/keeper/keeper.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/apps/transfer/ibc_module.go b/modules/apps/transfer/ibc_module.go index 26f1c533434..c784a91e933 100644 --- a/modules/apps/transfer/ibc_module.go +++ b/modules/apps/transfer/ibc_module.go @@ -162,7 +162,7 @@ func (im IBCModule) OnChanCloseConfirm( } // OnRecvPacket implements the IBCModule interface. A successful acknowledgement -// is returned if the packet data is succesfully decoded and the receive application +// is returned if the packet data is successfully decoded and the receive application // logic returns without error. func (im IBCModule) OnRecvPacket( ctx sdk.Context, diff --git a/modules/core/04-channel/keeper/keeper.go b/modules/core/04-channel/keeper/keeper.go index d1ea53ac180..65378039ad9 100644 --- a/modules/core/04-channel/keeper/keeper.go +++ b/modules/core/04-channel/keeper/keeper.go @@ -403,7 +403,7 @@ func (k Keeper) GetChannelClientState(ctx sdk.Context, portID, channelID string) return connection.ClientId, clientState, nil } -// GetConnection wraps the conenction keeper's GetConnection function. +// GetConnection wraps the connection keeper's GetConnection function. func (k Keeper) GetConnection(ctx sdk.Context, connectionID string) (exported.ConnectionI, error) { connection, found := k.connectionKeeper.GetConnection(ctx, connectionID) if !found { From 6f2bf38a7da63ea55d0717f66d252fae6f214414 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 3 Mar 2022 16:47:47 +0100 Subject: [PATCH 046/140] chore: fix broken link (#972) (#1066) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit 01cd4adf5cbaf5970ac3cdc989fe8d2d5817e357) Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> --- docs/ibc/relayer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ibc/relayer.md b/docs/ibc/relayer.md index c846f2c7320..ce3fabe252d 100644 --- a/docs/ibc/relayer.md +++ b/docs/ibc/relayer.md @@ -27,7 +27,7 @@ a module event emission with the attribute value `ibc_` (02-clien ### Subscribing with Tendermint -Calling the Tendermint RPC method `Subscribe` via [Tendermint's Websocket](https://docs.tendermint.com/master/rpc/) will return events using +Calling the Tendermint RPC method `Subscribe` via [Tendermint's Websocket](https://docs.tendermint.com/v0.35/rpc/) 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 From 2160689e0295106e5314afbf71bc84726bff2a9e Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Thu, 3 Mar 2022 21:18:41 +0100 Subject: [PATCH 047/140] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc976fd96cf..3267e5fd78c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,6 +87,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (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 ## [v2.0.2](https://github.com/cosmos/ibc-go/releases/tag/v2.0.2) - 2021-12-15 From 22bea13d3c8c2dba224f3c41bc1d10366e58e065 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 4 Mar 2022 12:35:28 +0100 Subject: [PATCH 048/140] small fixes for v2 to v3 migration (#1016) (#1074) * small fixes for v2 to v3 migration * review comment * Update v2-to-v3.md * add store upgrade documentation Co-authored-by: Carlos Rodriguez (cherry picked from commit a55ca886b322ad3f9589ee5f9b9acfe150a2471c) Co-authored-by: Carlos Rodriguez --- docs/migrations/v2-to-v3.md | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/docs/migrations/v2-to-v3.md b/docs/migrations/v2-to-v3.md index 1a9220abcbe..0618371a581 100644 --- a/docs/migrations/v2-to-v3.md +++ b/docs/migrations/v2-to-v3.md @@ -42,7 +42,6 @@ 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{ @@ -52,7 +51,7 @@ app.UpgradeKeeper.SetUpgradeHandler("v3", // create ICS27 Host submodule params hostParams := icahosttypes.Params{ HostEnabled: true, - AllowMessages: []string{"/cosmos.bank.v1beta1.MsgSend", ...], + AllowMessages: []string{"/cosmos.bank.v1beta1.MsgSend", ...}, } // initialize ICS27 module @@ -68,6 +67,22 @@ app.UpgradeKeeper.SetUpgradeHandler("v3", The host and controller submodule params only need to be set if you integrate those submodules. For example, if a chain chooses not to integrate a controller submodule, it does not need to set the controller params. +#### Add `StoreUpgrades` for ICS27 module + +For ICS27 it is also necessary to [manually add store upgrades](https://docs.cosmos.network/v0.44/core/upgrade.html#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{icatypes.ModuleName}, + } + + app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) +} +``` + +This ensures that the new module's stores are added to the multistore before the migrations begin. + ### 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. @@ -85,7 +100,7 @@ The migration code required may look like: // overwrite parameters as desired hostGenesisState.Params = icahosttypes.Params{ HostEnabled: true, - AllowMessages: []string{"/cosmos.bank.v1beta1.MsgSend", ...], + AllowMessages: []string{"/cosmos.bank.v1beta1.MsgSend", ...}, } icaGenesisState := icatypes.NewGenesisState(controllerGenesisState, hostGenesisState) From 227a609e0a62137f1bd75cd1b780c592943d8eaa Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 8 Mar 2022 12:22:41 +0100 Subject: [PATCH 049/140] call packet.GetSequence() rather than passing the func as argument (backport #995) (#1083) * call packet.GetSequence() rather than passing the func as argument (#995) (cherry picked from commit fc452ac42d06a0119d0035a524fe90dc8e4cd466) * fix changelog Co-authored-by: Joe Bowman Co-authored-by: Carlos Rodriguez --- CHANGELOG.md | 1 + modules/core/04-channel/keeper/packet.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3267e5fd78c..ae3b54d2ff6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,6 +88,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (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.0.2](https://github.com/cosmos/ibc-go/releases/tag/v2.0.2) - 2021-12-15 diff --git a/modules/core/04-channel/keeper/packet.go b/modules/core/04-channel/keeper/packet.go index 5879c9ecb08..b54926b85e9 100644 --- a/modules/core/04-channel/keeper/packet.go +++ b/modules/core/04-channel/keeper/packet.go @@ -494,7 +494,7 @@ func (k Keeper) AcknowledgePacket( // log that a packet has been acknowledged k.Logger(ctx).Info( "packet acknowledged", - "sequence", packet.GetSequence, + "sequence", packet.GetSequence(), "src_port", packet.GetSourcePort(), "src_channel", packet.GetSourceChannel(), "dst_port", packet.GetDestPort(), From 5c0bf8b8a0f79643e36be98fb9883ea163d2d93a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 9 Mar 2022 11:52:02 +0100 Subject: [PATCH 050/140] Add counterpartyChannelID param to IBCModule.OnChanOpenAck (#1086) (#1087) * add counterpartyChannelID param to IBCModule OnChanOpenAck() * change testing mock * change ica IBCModules ChannelOpenAck * change transfer IBCModules ChannelOpenAck * change core keeper ChannelOpenAck() * CHANGELOG.md * update v2-to-v3 migration doc * Update docs/migrations/v2-to-v3.md Co-authored-by: Carlos Rodriguez Co-authored-by: Carlos Rodriguez (cherry picked from commit 13df199d70c4e3bd55419182270413491ee8a6d1) Co-authored-by: khanh <50263489+catShaark@users.noreply.github.com> --- CHANGELOG.md | 1 + docs/migrations/v2-to-v3.md | 4 ++++ modules/apps/27-interchain-accounts/controller/ibc_module.go | 3 ++- .../apps/27-interchain-accounts/controller/ibc_module_test.go | 4 ++-- modules/apps/27-interchain-accounts/host/ibc_module.go | 1 + modules/apps/transfer/ibc_module.go | 1 + modules/apps/transfer/ibc_module_test.go | 2 +- modules/core/05-port/types/module.go | 1 + modules/core/keeper/msg_server.go | 2 +- testing/mock/ibc_app.go | 1 + testing/mock/ibc_module.go | 4 ++-- 11 files changed, 17 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae3b54d2ff6..969fedc24f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### API Breaking +* (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 diff --git a/docs/migrations/v2-to-v3.md b/docs/migrations/v2-to-v3.md index 0618371a581..ce4744565f8 100644 --- a/docs/migrations/v2-to-v3.md +++ b/docs/migrations/v2-to-v3.md @@ -120,6 +120,10 @@ IBC applications must perform application version negoitation in `OnChanOpenTry` 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. diff --git a/modules/apps/27-interchain-accounts/controller/ibc_module.go b/modules/apps/27-interchain-accounts/controller/ibc_module.go index 1aa362a4247..c00c9f5d1c2 100644 --- a/modules/apps/27-interchain-accounts/controller/ibc_module.go +++ b/modules/apps/27-interchain-accounts/controller/ibc_module.go @@ -80,6 +80,7 @@ func (im IBCModule) OnChanOpenAck( ctx sdk.Context, portID, channelID string, + counterpartyChannelID string, counterpartyVersion string, ) error { if !im.keeper.IsControllerEnabled(ctx) { @@ -91,7 +92,7 @@ func (im IBCModule) OnChanOpenAck( } // call underlying app's OnChanOpenAck callback with the counterparty app version. - return im.app.OnChanOpenAck(ctx, portID, channelID, counterpartyVersion) + return im.app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) } // OnChanOpenAck implements the IBCModule interface diff --git a/modules/apps/27-interchain-accounts/controller/ibc_module_test.go b/modules/apps/27-interchain-accounts/controller/ibc_module_test.go index f2b5d36ac63..db4412d144e 100644 --- a/modules/apps/27-interchain-accounts/controller/ibc_module_test.go +++ b/modules/apps/27-interchain-accounts/controller/ibc_module_test.go @@ -285,7 +285,7 @@ func (suite *InterchainAccountsTestSuite) TestOnChanOpenAck() { { "ICA auth module callback fails", func() { suite.chainA.GetSimApp().ICAAuthModule.IBCApp.OnChanOpenAck = func( - ctx sdk.Context, portID, channelID string, counterpartyVersion string, + ctx sdk.Context, portID, channelID string, counterpartyChannelID string, counterpartyVersion string, ) error { return fmt.Errorf("mock ica auth fails") } @@ -316,7 +316,7 @@ func (suite *InterchainAccountsTestSuite) TestOnChanOpenAck() { cbs, ok := suite.chainA.App.GetIBCKeeper().Router.GetRoute(module) suite.Require().True(ok) - err = cbs.OnChanOpenAck(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.Version) + err = cbs.OnChanOpenAck(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelID, path.EndpointB.ChannelConfig.Version) if tc.expPass { suite.Require().NoError(err) diff --git a/modules/apps/27-interchain-accounts/host/ibc_module.go b/modules/apps/27-interchain-accounts/host/ibc_module.go index 630c4d44fc1..fb403c71937 100644 --- a/modules/apps/27-interchain-accounts/host/ibc_module.go +++ b/modules/apps/27-interchain-accounts/host/ibc_module.go @@ -61,6 +61,7 @@ func (im IBCModule) OnChanOpenAck( ctx sdk.Context, portID, channelID string, + counterpartyChannelID string, counterpartyVersion string, ) error { return sdkerrors.Wrap(icatypes.ErrInvalidChannelFlow, "channel handshake must be initiated by controller chain") diff --git a/modules/apps/transfer/ibc_module.go b/modules/apps/transfer/ibc_module.go index c784a91e933..f5ed807d8b2 100644 --- a/modules/apps/transfer/ibc_module.go +++ b/modules/apps/transfer/ibc_module.go @@ -125,6 +125,7 @@ func (im IBCModule) OnChanOpenAck( ctx sdk.Context, portID, channelID string, + _ string, counterpartyVersion string, ) error { if counterpartyVersion != types.Version { diff --git a/modules/apps/transfer/ibc_module_test.go b/modules/apps/transfer/ibc_module_test.go index b5f834a3a8e..92d0f30d4c7 100644 --- a/modules/apps/transfer/ibc_module_test.go +++ b/modules/apps/transfer/ibc_module_test.go @@ -228,7 +228,7 @@ func (suite *TransferTestSuite) TestOnChanOpenAck() { tc.malleate() // explicitly change fields in channel and testChannel - err = cbs.OnChanOpenAck(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, counterpartyVersion) + err = cbs.OnChanOpenAck(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointA.Counterparty.ChannelID, counterpartyVersion) if tc.expPass { suite.Require().NoError(err) diff --git a/modules/core/05-port/types/module.go b/modules/core/05-port/types/module.go index dea418b7250..9f754fe0a3e 100644 --- a/modules/core/05-port/types/module.go +++ b/modules/core/05-port/types/module.go @@ -51,6 +51,7 @@ type IBCModule interface { ctx sdk.Context, portID, channelID string, + counterpartyChannelID string, counterpartyVersion string, ) error diff --git a/modules/core/keeper/msg_server.go b/modules/core/keeper/msg_server.go index b19041f636d..a380540614c 100644 --- a/modules/core/keeper/msg_server.go +++ b/modules/core/keeper/msg_server.go @@ -261,7 +261,7 @@ func (k Keeper) ChannelOpenAck(goCtx context.Context, msg *channeltypes.MsgChann } // Perform application logic callback - if err = cbs.OnChanOpenAck(ctx, msg.PortId, msg.ChannelId, msg.CounterpartyVersion); err != nil { + if err = cbs.OnChanOpenAck(ctx, msg.PortId, msg.ChannelId, msg.CounterpartyChannelId, msg.CounterpartyVersion); err != nil { return nil, sdkerrors.Wrap(err, "channel open ack callback failed") } diff --git a/testing/mock/ibc_app.go b/testing/mock/ibc_app.go index 15f77d02d5a..77eb17b8c6f 100644 --- a/testing/mock/ibc_app.go +++ b/testing/mock/ibc_app.go @@ -40,6 +40,7 @@ type MockIBCApp struct { ctx sdk.Context, portID, channelID string, + counterpartyChannelID string, counterpartyVersion string, ) error diff --git a/testing/mock/ibc_module.go b/testing/mock/ibc_module.go index 1ea1d3850ad..e58f6ae7156 100644 --- a/testing/mock/ibc_module.go +++ b/testing/mock/ibc_module.go @@ -64,9 +64,9 @@ func (im IBCModule) OnChanOpenTry( } // OnChanOpenAck implements the IBCModule interface. -func (im IBCModule) OnChanOpenAck(ctx sdk.Context, portID string, channelID string, counterpartyVersion string) error { +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, counterpartyVersion) + return im.IBCApp.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) } return nil From 549d9bf7a4f7a2f9c7ca6a9a7edb710bf2d0413c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 9 Mar 2022 13:26:06 +0000 Subject: [PATCH 051/140] fix mirgation docs (#1091) (#1092) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit 90a7e5fbdd8f1951fd7f787d0f74449bf7c21831) Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> --- docs/migrations/v2-to-v3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/migrations/v2-to-v3.md b/docs/migrations/v2-to-v3.md index ce4744565f8..30550d9be04 100644 --- a/docs/migrations/v2-to-v3.md +++ b/docs/migrations/v2-to-v3.md @@ -74,7 +74,7 @@ For ICS27 it is also necessary to [manually add store upgrades](https://docs.cos ```go if upgradeInfo.Name == "v3" && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { storeUpgrades := store.StoreUpgrades{ - Added: []string{icatypes.ModuleName}, + Added: []string{icacontrollertypes.StoreKey, icahosttypes.StoreKey}, } app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) From 45fdb81dc45ee8d20a748330af05c3bc2994eca6 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 10 Mar 2022 10:25:32 +0100 Subject: [PATCH 052/140] fix: handle testing update client errors (#1094) (#1095) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit a7563c9bc03080706788fafcecdf948849187d04) Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> --- testing/coordinator.go | 6 ++++-- testing/endpoint.go | 18 ++++++++++++------ testing/path.go | 10 +++++++--- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/testing/coordinator.go b/testing/coordinator.go index 5a7a91650fc..65bf5a4f1f3 100644 --- a/testing/coordinator.go +++ b/testing/coordinator.go @@ -117,7 +117,8 @@ func (coord *Coordinator) CreateConnections(path *Path) { require.NoError(coord.T, err) // ensure counterparty is up to date - path.EndpointA.UpdateClient() + err = path.EndpointA.UpdateClient() + require.NoError(coord.T, err) } // CreateMockChannels constructs and executes channel handshake messages to create OPEN @@ -158,7 +159,8 @@ func (coord *Coordinator) CreateChannels(path *Path) { require.NoError(coord.T, err) // ensure counterparty is up to date - path.EndpointA.UpdateClient() + err = path.EndpointA.UpdateClient() + require.NoError(coord.T, err) } // GetChain returns the TestChain using the given chainID and returns an error if it does diff --git a/testing/endpoint.go b/testing/endpoint.go index 5d496494afc..5c2e2e33f4f 100644 --- a/testing/endpoint.go +++ b/testing/endpoint.go @@ -175,7 +175,8 @@ func (endpoint *Endpoint) ConnOpenInit() error { // ConnOpenTry will construct and execute a MsgConnectionOpenTry on the associated endpoint. func (endpoint *Endpoint) ConnOpenTry() error { - endpoint.UpdateClient() + err := endpoint.UpdateClient() + require.NoError(endpoint.Chain.T, err) counterpartyClient, proofClient, proofConsensus, consensusHeight, proofInit, proofHeight := endpoint.QueryConnectionHandshakeProof() @@ -202,7 +203,8 @@ func (endpoint *Endpoint) ConnOpenTry() error { // ConnOpenAck will construct and execute a MsgConnectionOpenAck on the associated endpoint. func (endpoint *Endpoint) ConnOpenAck() error { - endpoint.UpdateClient() + err := endpoint.UpdateClient() + require.NoError(endpoint.Chain.T, err) counterpartyClient, proofClient, proofConsensus, consensusHeight, proofTry, proofHeight := endpoint.QueryConnectionHandshakeProof() @@ -218,7 +220,8 @@ func (endpoint *Endpoint) ConnOpenAck() error { // ConnOpenConfirm will construct and execute a MsgConnectionOpenConfirm on the associated endpoint. func (endpoint *Endpoint) ConnOpenConfirm() error { - endpoint.UpdateClient() + err := endpoint.UpdateClient() + require.NoError(endpoint.Chain.T, err) connectionKey := host.ConnectionKey(endpoint.Counterparty.ConnectionID) proof, height := endpoint.Counterparty.Chain.QueryProof(connectionKey) @@ -281,7 +284,8 @@ func (endpoint *Endpoint) ChanOpenInit() error { // ChanOpenTry will construct and execute a MsgChannelOpenTry on the associated endpoint. func (endpoint *Endpoint) ChanOpenTry() error { - endpoint.UpdateClient() + err := endpoint.UpdateClient() + require.NoError(endpoint.Chain.T, err) channelKey := host.ChannelKey(endpoint.Counterparty.ChannelConfig.PortID, endpoint.Counterparty.ChannelID) proof, height := endpoint.Counterparty.Chain.QueryProof(channelKey) @@ -312,7 +316,8 @@ func (endpoint *Endpoint) ChanOpenTry() error { // ChanOpenAck will construct and execute a MsgChannelOpenAck on the associated endpoint. func (endpoint *Endpoint) ChanOpenAck() error { - endpoint.UpdateClient() + err := endpoint.UpdateClient() + require.NoError(endpoint.Chain.T, err) channelKey := host.ChannelKey(endpoint.Counterparty.ChannelConfig.PortID, endpoint.Counterparty.ChannelID) proof, height := endpoint.Counterparty.Chain.QueryProof(channelKey) @@ -328,7 +333,8 @@ func (endpoint *Endpoint) ChanOpenAck() error { // ChanOpenConfirm will construct and execute a MsgChannelOpenConfirm on the associated endpoint. func (endpoint *Endpoint) ChanOpenConfirm() error { - endpoint.UpdateClient() + err := endpoint.UpdateClient() + require.NoError(endpoint.Chain.T, err) channelKey := host.ChannelKey(endpoint.Counterparty.ChannelConfig.PortID, endpoint.Counterparty.ChannelID) proof, height := endpoint.Counterparty.Chain.QueryProof(channelKey) diff --git a/testing/path.go b/testing/path.go index d447102c7b9..731d3cd5e1b 100644 --- a/testing/path.go +++ b/testing/path.go @@ -43,7 +43,9 @@ func (path *Path) RelayPacket(packet channeltypes.Packet) error { if bytes.Equal(pc, channeltypes.CommitPacket(path.EndpointA.Chain.App.AppCodec(), packet)) { // packet found, relay from A to B - path.EndpointB.UpdateClient() + if err := path.EndpointB.UpdateClient(); err != nil { + return err + } res, err := path.EndpointB.RecvPacketWithResult(packet) if err != nil { @@ -58,15 +60,17 @@ func (path *Path) RelayPacket(packet channeltypes.Packet) error { if err := path.EndpointA.AcknowledgePacket(packet, ack); err != nil { return err } - return nil + return 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(path.EndpointB.Chain.App.AppCodec(), packet)) { // packet found, relay B to A - path.EndpointA.UpdateClient() + if err := path.EndpointA.UpdateClient(); err != nil { + return err + } res, err := path.EndpointA.RecvPacketWithResult(packet) if err != nil { From 16c65c622598c33991b3049c41c6b48119644f74 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 10 Mar 2022 13:04:42 +0100 Subject: [PATCH 053/140] replace channel keeper with IBC keeper in AnteDecorator (#950) (#1096) * replace channel keeper with IBC keeper in AnteDecorator and pass message to rpc handler * fix error checking condition * fix for proper way of getting go context * refactor tests for ante handler * review comment * review comments and some fixes * review comments * execute message for update client as well * add migration Co-authored-by: Carlos Rodriguez (cherry picked from commit f0b94dfa992d0baf777d9fb3d5ac896d543a8a4e) Co-authored-by: Carlos Rodriguez --- CHANGELOG.md | 1 + docs/ibc/proto-docs.md | 35 ++ docs/migrations/v2-to-v3.md | 16 + modules/core/04-channel/types/tx.pb.go | 299 ++++++++++--- modules/core/ante/ante.go | 45 +- modules/core/ante/ante_test.go | 595 +++++++++++++------------ modules/core/keeper/msg_server.go | 16 +- proto/ibc/core/channel/v1/tx.proto | 36 +- testing/endpoint.go | 30 ++ testing/simapp/ante_handler.go | 9 +- testing/simapp/app.go | 2 +- 11 files changed, 683 insertions(+), 401 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 969fedc24f0..0629d3ebf13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (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 diff --git a/docs/ibc/proto-docs.md b/docs/ibc/proto-docs.md index 79d56a6a541..80b2df196d3 100644 --- a/docs/ibc/proto-docs.md +++ b/docs/ibc/proto-docs.md @@ -127,6 +127,8 @@ - [MsgTimeoutOnCloseResponse](#ibc.core.channel.v1.MsgTimeoutOnCloseResponse) - [MsgTimeoutResponse](#ibc.core.channel.v1.MsgTimeoutResponse) + - [ResponseResultType](#ibc.core.channel.v1.ResponseResultType) + - [Msg](#ibc.core.channel.v1.Msg) - [ibc/core/client/v1/genesis.proto](#ibc/core/client/v1/genesis.proto) @@ -1738,6 +1740,11 @@ MsgAcknowledgement receives incoming IBC acknowledgement MsgAcknowledgementResponse defines the Msg/Acknowledgement response type. +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `result` | [ResponseResultType](#ibc.core.channel.v1.ResponseResultType) | | | + + @@ -1954,6 +1961,11 @@ MsgRecvPacket receives incoming IBC packet MsgRecvPacketResponse defines the Msg/RecvPacket response type. +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `result` | [ResponseResultType](#ibc.core.channel.v1.ResponseResultType) | | | + + @@ -2003,6 +2015,11 @@ MsgTimeoutOnClose timed-out packet upon counterparty channel closure. MsgTimeoutOnCloseResponse defines the Msg/TimeoutOnClose response type. +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `result` | [ResponseResultType](#ibc.core.channel.v1.ResponseResultType) | | | + + @@ -2013,11 +2030,29 @@ MsgTimeoutOnCloseResponse defines the Msg/TimeoutOnClose response type. MsgTimeoutResponse defines the Msg/Timeout response type. +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `result` | [ResponseResultType](#ibc.core.channel.v1.ResponseResultType) | | | + + + + + +### ResponseResultType +ResponseResultType defines the possible outcomes of the execution of a message + +| Name | Number | Description | +| ---- | ------ | ----------- | +| RESPONSE_RESULT_UNSPECIFIED | 0 | Default zero value enumeration | +| RESPONSE_RESULT_NOOP | 1 | The message did not call the IBC application callbacks (because, for example, the packet had already been relayed) | +| RESPONSE_RESULT_SUCCESS | 2 | The message was executed successfully | + + diff --git a/docs/migrations/v2-to-v3.md b/docs/migrations/v2-to-v3.md index 30550d9be04..37224134b6b 100644 --- a/docs/migrations/v2-to-v3.md +++ b/docs/migrations/v2-to-v3.md @@ -109,6 +109,22 @@ The migration code required may look like: 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 diff --git a/modules/core/04-channel/types/tx.pb.go b/modules/core/04-channel/types/tx.pb.go index 6fcc1a44276..e497cf802b1 100644 --- a/modules/core/04-channel/types/tx.pb.go +++ b/modules/core/04-channel/types/tx.pb.go @@ -29,6 +29,38 @@ var _ = math.Inf // 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 +) + +var ResponseResultType_name = map[int32]string{ + 0: "RESPONSE_RESULT_UNSPECIFIED", + 1: "RESPONSE_RESULT_NOOP", + 2: "RESPONSE_RESULT_SUCCESS", +} + +var ResponseResultType_value = map[string]int32{ + "RESPONSE_RESULT_UNSPECIFIED": 0, + "RESPONSE_RESULT_NOOP": 1, + "RESPONSE_RESULT_SUCCESS": 2, +} + +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 { @@ -566,6 +598,7 @@ 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{} } @@ -645,6 +678,7 @@ 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{} } @@ -725,6 +759,7 @@ 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{} } @@ -804,6 +839,7 @@ 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{} } @@ -840,6 +876,7 @@ func (m *MsgAcknowledgementResponse) XXX_DiscardUnknown() { 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") @@ -865,79 +902,87 @@ func init() { func init() { proto.RegisterFile("ibc/core/channel/v1/tx.proto", fileDescriptor_bc4637e0ac3fc7b7) } var fileDescriptor_bc4637e0ac3fc7b7 = []byte{ - // 1138 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0x4d, 0x6f, 0xdb, 0x46, - 0x13, 0xd6, 0x87, 0x2d, 0xdb, 0x63, 0xbf, 0xb1, 0x4d, 0xf9, 0x43, 0xa1, 0x6c, 0xd1, 0x2f, 0x0f, - 0x89, 0x91, 0x22, 0x62, 0x6c, 0x07, 0x28, 0x12, 0xf4, 0x62, 0x09, 0x28, 0x6a, 0x14, 0x6e, 0x0a, - 0xda, 0xed, 0xc1, 0x28, 0x20, 0x48, 0xab, 0x0d, 0x45, 0x48, 0xe2, 0xaa, 0x24, 0xa5, 0x44, 0xff, - 0xa0, 0xc7, 0x9c, 0x7b, 0x4a, 0xcf, 0x3d, 0xa4, 0x3f, 0x23, 0xc7, 0x9c, 0xda, 0xa2, 0x07, 0xa2, - 0xb0, 0x2f, 0x3d, 0xf3, 0x17, 0x14, 0x5c, 0x2e, 0x3f, 0x24, 0x91, 0x15, 0x95, 0x54, 0x6e, 0x6e, - 0xe4, 0xcc, 0xb3, 0xb3, 0xb3, 0xcf, 0x33, 0x9c, 0xdd, 0x25, 0xec, 0xa9, 0x0d, 0x24, 0x21, 0xa2, - 0x63, 0x09, 0xb5, 0xea, 0x9a, 0x86, 0x3b, 0xd2, 0xe0, 0x48, 0x32, 0x5f, 0x96, 0x7b, 0x3a, 0x31, - 0x09, 0x97, 0x57, 0x1b, 0xa8, 0xec, 0x78, 0xcb, 0xcc, 0x5b, 0x1e, 0x1c, 0xf1, 0x5b, 0x0a, 0x51, - 0x08, 0xf5, 0x4b, 0xce, 0x93, 0x0b, 0xe5, 0x85, 0x20, 0x50, 0x47, 0xc5, 0x9a, 0xe9, 0xc4, 0x71, - 0x9f, 0x18, 0xe0, 0xff, 0x51, 0x33, 0x79, 0x61, 0x29, 0x44, 0xfc, 0x29, 0x0d, 0xdc, 0xb9, 0xa1, - 0x54, 0x5d, 0xe3, 0xb3, 0x1e, 0xd6, 0xce, 0x34, 0xd5, 0xe4, 0x3e, 0x81, 0xa5, 0x1e, 0xd1, 0xcd, - 0x9a, 0xda, 0x2c, 0xa4, 0x0f, 0xd2, 0x87, 0x2b, 0x15, 0xce, 0xb6, 0x84, 0x3b, 0xc3, 0x7a, 0xb7, - 0xf3, 0x54, 0x64, 0x0e, 0x51, 0xce, 0x39, 0x4f, 0x67, 0x4d, 0xee, 0x33, 0x58, 0x62, 0x41, 0x0b, - 0x99, 0x83, 0xf4, 0xe1, 0xea, 0xf1, 0x5e, 0x39, 0x62, 0x11, 0x65, 0x36, 0x47, 0x65, 0xe1, 0xad, - 0x25, 0xa4, 0x64, 0x6f, 0x08, 0xb7, 0x03, 0x39, 0x43, 0x55, 0x34, 0xac, 0x17, 0xb2, 0xce, 0x4c, - 0x32, 0x7b, 0x7b, 0xba, 0xfc, 0xc3, 0x6b, 0x21, 0xf5, 0xd7, 0x6b, 0x21, 0x25, 0xca, 0xc0, 0x4f, - 0xa6, 0x28, 0x63, 0xa3, 0x47, 0x34, 0x03, 0x73, 0x8f, 0x01, 0x58, 0xa8, 0x20, 0xdb, 0x6d, 0xdb, - 0x12, 0x36, 0xdd, 0x6c, 0x03, 0x9f, 0x28, 0xaf, 0xb0, 0x97, 0xb3, 0xa6, 0xf8, 0x6b, 0x16, 0x36, - 0x47, 0x83, 0x5e, 0xea, 0xc3, 0xd9, 0x96, 0xfd, 0x15, 0xe4, 0x7b, 0x3a, 0x1e, 0xa8, 0xa4, 0x6f, - 0xd4, 0x42, 0x19, 0x64, 0xe8, 0xc0, 0x92, 0x6d, 0x09, 0x3c, 0x1b, 0x38, 0x09, 0x12, 0xe5, 0x4d, - 0xcf, 0x5a, 0xf5, 0x52, 0x0a, 0xd3, 0x98, 0x9d, 0x9d, 0x46, 0x19, 0xb6, 0x10, 0xe9, 0x6b, 0x26, - 0xd6, 0x7b, 0x75, 0xdd, 0x1c, 0xd6, 0x06, 0x58, 0x37, 0x54, 0xa2, 0x15, 0x16, 0x68, 0x3a, 0x82, - 0x6d, 0x09, 0x45, 0x46, 0x48, 0x04, 0x4a, 0x94, 0xf3, 0x61, 0xf3, 0xb7, 0xae, 0xd5, 0xa1, 0xb6, - 0xa7, 0x13, 0xf2, 0xbc, 0xa6, 0x6a, 0xaa, 0x59, 0x58, 0x3c, 0x48, 0x1f, 0xae, 0x85, 0xa9, 0x0d, - 0x7c, 0xa2, 0xbc, 0x42, 0x5f, 0x68, 0xed, 0x5c, 0xc1, 0x9a, 0xeb, 0x69, 0x61, 0x55, 0x69, 0x99, - 0x85, 0x1c, 0x5d, 0x0c, 0x1f, 0x5a, 0x8c, 0x5b, 0xa3, 0x83, 0xa3, 0xf2, 0x17, 0x14, 0x51, 0x29, - 0x3a, 0x4b, 0xb1, 0x2d, 0x21, 0x1f, 0x8e, 0xeb, 0x8e, 0x16, 0xe5, 0x55, 0xfa, 0xea, 0x22, 0x43, - 0xc5, 0xb2, 0x14, 0x53, 0x2c, 0x45, 0xb8, 0x3b, 0xa1, 0xab, 0x57, 0x2b, 0xe2, 0x6f, 0x13, 0xaa, - 0x9f, 0xa2, 0xf6, 0x6c, 0xaa, 0x8f, 0x96, 0x5b, 0x26, 0x59, 0xb9, 0x71, 0x57, 0xb0, 0x3b, 0xc2, - 0x7b, 0x28, 0x04, 0xad, 0xfa, 0x8a, 0x68, 0x5b, 0x42, 0x29, 0x42, 0xa0, 0x70, 0xbc, 0xed, 0xb0, - 0x27, 0xa8, 0x9b, 0x79, 0x28, 0x7f, 0x04, 0xae, 0xa0, 0x35, 0x53, 0x1f, 0x32, 0xe1, 0xb7, 0x6c, - 0x4b, 0xd8, 0x08, 0x0b, 0x64, 0xea, 0x43, 0x51, 0x5e, 0xa6, 0xcf, 0xce, 0xb7, 0xf3, 0x91, 0xc9, - 0x7e, 0x8a, 0xda, 0xbe, 0xec, 0x3f, 0x67, 0x60, 0x7b, 0xd4, 0x5b, 0x25, 0xda, 0x73, 0x55, 0xef, - 0xde, 0x86, 0xf4, 0x3e, 0x95, 0x75, 0xd4, 0xa6, 0x62, 0x47, 0x50, 0x59, 0x47, 0x6d, 0x8f, 0x4a, - 0xa7, 0x20, 0xc7, 0xa9, 0x5c, 0x98, 0x0b, 0x95, 0x8b, 0x31, 0x54, 0x0a, 0xb0, 0x1f, 0x49, 0x96, - 0x4f, 0xe7, 0x8f, 0x69, 0xc8, 0x07, 0x88, 0x6a, 0x87, 0x18, 0x78, 0xf6, 0x4d, 0xe3, 0xfd, 0xc8, - 0x9c, 0xbe, 0x59, 0xec, 0x43, 0x31, 0x22, 0x37, 0x3f, 0xf7, 0x37, 0x19, 0xd8, 0x19, 0xf3, 0xdf, - 0x62, 0x2d, 0x8c, 0x36, 0xd4, 0xec, 0x7b, 0x36, 0xd4, 0xdb, 0x2d, 0x87, 0x03, 0x28, 0x45, 0x13, - 0xe6, 0x73, 0xfa, 0x2a, 0x03, 0xff, 0x3b, 0x37, 0x14, 0x19, 0xa3, 0xc1, 0xd7, 0x75, 0xd4, 0xc6, - 0x26, 0xf7, 0x04, 0x72, 0x3d, 0xfa, 0x44, 0x99, 0x5c, 0x3d, 0x2e, 0x46, 0xee, 0x64, 0x2e, 0x98, - 0x6d, 0x64, 0x6c, 0x00, 0xf7, 0x39, 0x6c, 0xb8, 0xe9, 0x22, 0xd2, 0xed, 0xaa, 0x66, 0x17, 0x6b, - 0x26, 0xa5, 0x77, 0xad, 0x52, 0xb4, 0x2d, 0x61, 0x37, 0xbc, 0xa0, 0x00, 0x21, 0xca, 0xeb, 0xd4, - 0x54, 0xf5, 0x2d, 0x13, 0xa4, 0x65, 0xe7, 0x42, 0xda, 0x42, 0x0c, 0x69, 0xbb, 0xb4, 0xe1, 0x04, - 0x8c, 0xf8, 0x5c, 0xfd, 0x91, 0x01, 0x38, 0x37, 0x94, 0x4b, 0xb5, 0x8b, 0x49, 0xff, 0xdf, 0x21, - 0xaa, 0xaf, 0xe9, 0x18, 0x61, 0x75, 0x80, 0x9b, 0x71, 0x44, 0x05, 0x08, 0x8f, 0xa8, 0x6f, 0x7c, - 0xcb, 0x5c, 0x89, 0xfa, 0x12, 0x38, 0x0d, 0xbf, 0x34, 0x6b, 0x06, 0xfe, 0xbe, 0x8f, 0x35, 0x84, - 0x6b, 0x3a, 0x46, 0x03, 0x4a, 0xda, 0x42, 0x65, 0xdf, 0xb6, 0x84, 0xbb, 0x6e, 0x84, 0x49, 0x8c, - 0x28, 0x6f, 0x38, 0xc6, 0x0b, 0x66, 0x73, 0x88, 0x4c, 0x50, 0xaa, 0x5b, 0xf4, 0x2c, 0xcb, 0xb8, - 0x0d, 0xda, 0x95, 0xbb, 0xe9, 0x33, 0xf3, 0x33, 0x8d, 0xd6, 0xf0, 0xc7, 0xc0, 0xfc, 0xa7, 0xb0, - 0xca, 0x0a, 0xd9, 0xc9, 0x88, 0xb5, 0x83, 0x1d, 0xdb, 0x12, 0xb8, 0x91, 0x2a, 0x77, 0x9c, 0xa2, - 0xec, 0x36, 0x0e, 0x37, 0xf7, 0x79, 0x36, 0x84, 0x68, 0xc9, 0x16, 0x3f, 0x54, 0xb2, 0xdc, 0x3f, - 0xee, 0xdb, 0xa3, 0xda, 0xf8, 0xca, 0xfd, 0x92, 0xa1, 0x82, 0x9e, 0xa2, 0xb6, 0x46, 0x5e, 0x74, - 0x70, 0x53, 0xc1, 0xf4, 0xd3, 0xfe, 0x00, 0xe9, 0x0e, 0x61, 0xbd, 0x3e, 0x1a, 0xcd, 0x55, 0x4e, - 0x1e, 0x37, 0x07, 0xe2, 0x38, 0x03, 0x9b, 0x71, 0xe2, 0x50, 0xa7, 0x27, 0xce, 0xa9, 0xf3, 0xf2, - 0x1f, 0x77, 0xeb, 0x3d, 0x7a, 0x57, 0x1a, 0x63, 0xcc, 0x23, 0xf4, 0xf8, 0xcd, 0x32, 0x64, 0xcf, - 0x0d, 0x85, 0x6b, 0xc3, 0xfa, 0xf8, 0x8d, 0xef, 0x7e, 0x24, 0x89, 0x93, 0xf7, 0x2e, 0x5e, 0x4a, - 0x08, 0xf4, 0x2f, 0x68, 0x2d, 0xb8, 0x33, 0x76, 0xcd, 0xba, 0x97, 0x20, 0xc4, 0xa5, 0x3e, 0xe4, - 0xcb, 0xc9, 0x70, 0x31, 0x33, 0x39, 0x27, 0xa9, 0x24, 0x33, 0x9d, 0xa2, 0x76, 0xa2, 0x99, 0x42, - 0x27, 0x4a, 0xce, 0x04, 0x2e, 0xe2, 0x34, 0xf9, 0x20, 0x41, 0x14, 0x86, 0xe5, 0x8f, 0x93, 0x63, - 0xfd, 0x59, 0x35, 0xd8, 0x98, 0x38, 0x74, 0x1d, 0x4e, 0x89, 0xe3, 0x23, 0xf9, 0x47, 0x49, 0x91, - 0xfe, 0x7c, 0x2f, 0x20, 0x1f, 0x79, 0x50, 0x4a, 0x12, 0xc8, 0x5b, 0xe7, 0xc9, 0x0c, 0x60, 0x7f, - 0xe2, 0xef, 0x00, 0x42, 0xa7, 0x09, 0x31, 0x2e, 0x44, 0x80, 0xe1, 0x1f, 0x4c, 0xc7, 0xf8, 0xd1, - 0x2f, 0x60, 0xc9, 0xdb, 0x7f, 0x85, 0xb8, 0x61, 0x0c, 0xc0, 0xdf, 0x9f, 0x02, 0x08, 0xd7, 0xde, - 0xd8, 0x0e, 0x73, 0x6f, 0xca, 0x50, 0x86, 0x8b, 0xaf, 0xbd, 0xe8, 0xae, 0xe8, 0x7c, 0xbc, 0xe3, - 0x1d, 0x31, 0x36, 0xcb, 0x31, 0x60, 0xfc, 0xc7, 0x1b, 0xd3, 0x31, 0x2a, 0x17, 0x6f, 0xaf, 0x4b, - 0xe9, 0x77, 0xd7, 0xa5, 0xf4, 0x9f, 0xd7, 0xa5, 0xf4, 0xab, 0x9b, 0x52, 0xea, 0xdd, 0x4d, 0x29, - 0xf5, 0xfb, 0x4d, 0x29, 0x75, 0xf5, 0x44, 0x51, 0xcd, 0x56, 0xbf, 0x51, 0x46, 0xa4, 0x2b, 0x21, - 0x62, 0x74, 0x89, 0x21, 0xa9, 0x0d, 0xf4, 0x50, 0x21, 0xd2, 0xe0, 0x44, 0xea, 0x92, 0x66, 0xbf, - 0x83, 0x0d, 0xf7, 0xe7, 0xd3, 0xa3, 0xc7, 0x0f, 0xbd, 0xff, 0x4f, 0xe6, 0xb0, 0x87, 0x8d, 0x46, - 0x8e, 0xfe, 0x7b, 0x3a, 0xf9, 0x3b, 0x00, 0x00, 0xff, 0xff, 0xc5, 0x64, 0xa7, 0xfd, 0x0a, 0x13, - 0x00, 0x00, + // 1267 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0x4b, 0x6f, 0xdb, 0x46, + 0x10, 0xd6, 0xcb, 0xb2, 0x3d, 0x72, 0x6d, 0x99, 0xf2, 0x43, 0xa1, 0x62, 0x51, 0xe5, 0x21, 0x11, + 0x5c, 0x44, 0xf2, 0x23, 0x40, 0x11, 0xa3, 0x40, 0x61, 0xa9, 0x0a, 0x6a, 0xb4, 0x7e, 0x80, 0xb2, + 0x7b, 0x70, 0x8b, 0x0a, 0x12, 0xb5, 0x91, 0x09, 0x49, 0x5c, 0x95, 0xa4, 0x94, 0xe8, 0x1f, 0x04, + 0x3e, 0xe5, 0x6c, 0xc0, 0x40, 0x8a, 0x1e, 0x7b, 0x48, 0x7f, 0x46, 0x8e, 0x39, 0xb5, 0x45, 0x0f, + 0x42, 0x61, 0x5f, 0x7a, 0xd6, 0x2f, 0x28, 0xb8, 0x5c, 0x52, 0x94, 0x44, 0xc2, 0x74, 0x62, 0x3b, + 0xb9, 0xed, 0xce, 0x7c, 0x3b, 0x3b, 0xfb, 0x7d, 0xc3, 0x7d, 0x10, 0xee, 0x4b, 0x15, 0x31, 0x2b, + 0x62, 0x05, 0x65, 0xc5, 0x93, 0xb2, 0x2c, 0xa3, 0x46, 0xb6, 0xb3, 0x9e, 0xd5, 0x5e, 0x64, 0x5a, + 0x0a, 0xd6, 0x30, 0x13, 0x93, 0x2a, 0x62, 0x46, 0xf7, 0x66, 0xa8, 0x37, 0xd3, 0x59, 0x67, 0x17, + 0x6a, 0xb8, 0x86, 0x89, 0x3f, 0xab, 0xb7, 0x0c, 0x28, 0xcb, 0x0d, 0x02, 0x35, 0x24, 0x24, 0x6b, + 0x7a, 0x1c, 0xa3, 0x45, 0x01, 0x9f, 0x3b, 0xcd, 0x64, 0x86, 0x25, 0x10, 0xfe, 0x57, 0x3f, 0x30, + 0xbb, 0x6a, 0x2d, 0x6f, 0x18, 0xf7, 0x5b, 0x48, 0xde, 0x91, 0x25, 0x8d, 0xf9, 0x02, 0x26, 0x5b, + 0x58, 0xd1, 0x4a, 0x52, 0x35, 0xee, 0x4f, 0xf9, 0xd3, 0xd3, 0x39, 0xa6, 0xdf, 0xe3, 0x66, 0xbb, + 0xe5, 0x66, 0x63, 0x8b, 0xa7, 0x0e, 0x5e, 0x08, 0xeb, 0xad, 0x9d, 0x2a, 0xf3, 0x15, 0x4c, 0xd2, + 0xa0, 0xf1, 0x40, 0xca, 0x9f, 0x8e, 0x6c, 0xdc, 0xcf, 0x38, 0x2c, 0x22, 0x43, 0xe7, 0xc8, 0x85, + 0xde, 0xf6, 0x38, 0x9f, 0x60, 0x0e, 0x61, 0x96, 0x20, 0xac, 0x4a, 0x35, 0x19, 0x29, 0xf1, 0xa0, + 0x3e, 0x93, 0x40, 0x7b, 0x5b, 0x53, 0x2f, 0x5f, 0x73, 0xbe, 0xff, 0x5e, 0x73, 0x3e, 0x5e, 0x00, + 0x76, 0x3c, 0x45, 0x01, 0xa9, 0x2d, 0x2c, 0xab, 0x88, 0x79, 0x0c, 0x40, 0x43, 0x0d, 0xb2, 0x5d, + 0xec, 0xf7, 0xb8, 0x79, 0x23, 0xdb, 0x81, 0x8f, 0x17, 0xa6, 0x69, 0x67, 0xa7, 0xca, 0xff, 0x19, + 0x84, 0xf9, 0xe1, 0xa0, 0x87, 0x4a, 0xf7, 0x7a, 0xcb, 0xde, 0x83, 0x58, 0x4b, 0x41, 0x1d, 0x09, + 0xb7, 0xd5, 0x92, 0x2d, 0x83, 0x00, 0x19, 0x98, 0xec, 0xf7, 0x38, 0x96, 0x0e, 0x1c, 0x07, 0xf1, + 0xc2, 0xbc, 0x69, 0xcd, 0x9b, 0x29, 0xd9, 0x69, 0x0c, 0x5e, 0x9f, 0x46, 0x01, 0x16, 0x44, 0xdc, + 0x96, 0x35, 0xa4, 0xb4, 0xca, 0x8a, 0xd6, 0x2d, 0x75, 0x90, 0xa2, 0x4a, 0x58, 0x8e, 0x87, 0x48, + 0x3a, 0x5c, 0xbf, 0xc7, 0x25, 0x28, 0x21, 0x0e, 0x28, 0x5e, 0x88, 0xd9, 0xcd, 0x3f, 0x18, 0x56, + 0x9d, 0xda, 0x96, 0x82, 0xf1, 0xb3, 0x92, 0x24, 0x4b, 0x5a, 0x7c, 0x22, 0xe5, 0x4f, 0xcf, 0xd8, + 0xa9, 0x1d, 0xf8, 0x78, 0x61, 0x9a, 0x74, 0x48, 0xed, 0x1c, 0xc3, 0x8c, 0xe1, 0x39, 0x41, 0x52, + 0xed, 0x44, 0x8b, 0x87, 0xc9, 0x62, 0x58, 0xdb, 0x62, 0x8c, 0x1a, 0xed, 0xac, 0x67, 0xbe, 0x25, + 0x88, 0x5c, 0x42, 0x5f, 0x4a, 0xbf, 0xc7, 0xc5, 0xec, 0x71, 0x8d, 0xd1, 0xbc, 0x10, 0x21, 0x5d, + 0x03, 0x69, 0x2b, 0x96, 0x49, 0x97, 0x62, 0x49, 0xc0, 0xbd, 0x31, 0x5d, 0xcd, 0x5a, 0xe1, 0xff, + 0x1a, 0x53, 0x7d, 0x5b, 0xac, 0x5f, 0x4f, 0xf5, 0xe1, 0x72, 0x0b, 0x78, 0x2b, 0x37, 0xe6, 0x18, + 0x96, 0x87, 0x78, 0xb7, 0x85, 0x20, 0x55, 0x9f, 0xe3, 0xfb, 0x3d, 0x2e, 0xe9, 0x20, 0x90, 0x3d, + 0xde, 0xa2, 0xdd, 0x33, 0xa8, 0x9b, 0xdb, 0x50, 0x7e, 0x1d, 0x0c, 0x41, 0x4b, 0x9a, 0xd2, 0xa5, + 0xc2, 0x2f, 0xf4, 0x7b, 0x5c, 0xd4, 0x2e, 0x90, 0xa6, 0x74, 0x79, 0x61, 0x8a, 0xb4, 0xf5, 0x6f, + 0xe7, 0x13, 0x93, 0x7d, 0x5b, 0xac, 0x5b, 0xb2, 0xff, 0x1e, 0x80, 0xc5, 0x61, 0x6f, 0x1e, 0xcb, + 0xcf, 0x24, 0xa5, 0x79, 0x17, 0xd2, 0x5b, 0x54, 0x96, 0xc5, 0x3a, 0x11, 0xdb, 0x81, 0xca, 0xb2, + 0x58, 0x37, 0xa9, 0xd4, 0x0b, 0x72, 0x94, 0xca, 0xd0, 0xad, 0x50, 0x39, 0xe1, 0x42, 0x25, 0x07, + 0x2b, 0x8e, 0x64, 0x59, 0x74, 0x9e, 0xf9, 0x21, 0x36, 0x40, 0xe4, 0x1b, 0x58, 0x45, 0xd7, 0x3f, + 0x34, 0xde, 0x8f, 0xcc, 0xab, 0x0f, 0x8b, 0x15, 0x48, 0x38, 0xe4, 0x66, 0xe5, 0xfe, 0x26, 0x00, + 0x4b, 0x23, 0xfe, 0x3b, 0xac, 0x85, 0xe1, 0x0d, 0x35, 0xf8, 0x9e, 0x1b, 0xea, 0xdd, 0x96, 0x43, + 0x0a, 0x92, 0xce, 0x84, 0x59, 0x9c, 0xbe, 0x0a, 0xc0, 0x67, 0xbb, 0x6a, 0x4d, 0x40, 0x62, 0xe7, + 0xa0, 0x2c, 0xd6, 0x91, 0xc6, 0x3c, 0x81, 0x70, 0x8b, 0xb4, 0x08, 0x93, 0x91, 0x8d, 0x84, 0xe3, + 0x49, 0x66, 0x80, 0xe9, 0x41, 0x46, 0x07, 0x30, 0x4f, 0x21, 0x6a, 0xa4, 0x2b, 0xe2, 0x66, 0x53, + 0xd2, 0x9a, 0x48, 0xd6, 0x08, 0xbd, 0x33, 0xb9, 0x44, 0xbf, 0xc7, 0x2d, 0xdb, 0x17, 0x34, 0x40, + 0xf0, 0xc2, 0x1c, 0x31, 0xe5, 0x2d, 0xcb, 0x18, 0x69, 0xc1, 0x5b, 0x21, 0x2d, 0xe4, 0x42, 0xda, + 0xcf, 0x64, 0xc3, 0x19, 0x30, 0x62, 0xdd, 0x56, 0xbe, 0x86, 0xb0, 0x82, 0xd4, 0x76, 0xc3, 0x60, + 0x66, 0x76, 0xe3, 0xa1, 0x23, 0x33, 0x26, 0x5c, 0x20, 0xd0, 0xc3, 0x6e, 0x0b, 0x09, 0x74, 0xd8, + 0x56, 0x48, 0x9f, 0x83, 0xff, 0x27, 0x00, 0xb0, 0xab, 0xd6, 0x0e, 0xa5, 0x26, 0xc2, 0xed, 0x9b, + 0xe1, 0xbb, 0x2d, 0x2b, 0x48, 0x44, 0x52, 0x07, 0x55, 0xdd, 0xf8, 0x1e, 0x20, 0x4c, 0xbe, 0x8f, + 0x2c, 0xcb, 0xad, 0xf2, 0xfd, 0x1d, 0x30, 0x32, 0x7a, 0xa1, 0x95, 0x54, 0xf4, 0x4b, 0x1b, 0xc9, + 0x22, 0x2a, 0x29, 0x48, 0xec, 0x10, 0xee, 0x43, 0xb9, 0x95, 0x7e, 0x8f, 0xbb, 0x67, 0x44, 0x18, + 0xc7, 0xf0, 0x42, 0x54, 0x37, 0x16, 0xa9, 0x4d, 0xd7, 0xc3, 0x43, 0xc5, 0xff, 0x48, 0xae, 0xc4, + 0x94, 0xdb, 0x9b, 0x56, 0xee, 0xcc, 0xb8, 0x82, 0xd0, 0xe8, 0xfb, 0x32, 0xf9, 0xa2, 0x3e, 0x05, + 0x01, 0xbf, 0x84, 0x08, 0xfd, 0xac, 0xf4, 0x8c, 0xe8, 0xe6, 0xb4, 0xd4, 0xef, 0x71, 0xcc, 0xd0, + 0x37, 0xa7, 0x3b, 0x79, 0xc1, 0xd8, 0xc6, 0x8c, 0xdc, 0x6f, 0x73, 0x7b, 0x72, 0x56, 0x7e, 0xe2, + 0x43, 0x95, 0x0f, 0xbb, 0x28, 0x5f, 0x21, 0xb7, 0x88, 0x61, 0x6d, 0x6e, 0xba, 0x00, 0xfe, 0x08, + 0x90, 0xf2, 0xda, 0x16, 0xeb, 0x32, 0x7e, 0xde, 0x40, 0xd5, 0x1a, 0x22, 0xfb, 0xd5, 0x07, 0x54, + 0x40, 0x1a, 0xe6, 0xca, 0xc3, 0xd1, 0x8c, 0x02, 0x10, 0x46, 0xcd, 0x03, 0x8d, 0xf5, 0x81, 0x55, + 0x37, 0x8d, 0x89, 0xd3, 0xd4, 0x78, 0x5b, 0xef, 0x7c, 0xe4, 0x23, 0x48, 0x24, 0x0f, 0xc0, 0x11, + 0xc6, 0x6e, 0x58, 0x97, 0xd5, 0x33, 0x3f, 0x30, 0xe3, 0x20, 0x66, 0x0d, 0x12, 0x42, 0xa1, 0x78, + 0xb0, 0xbf, 0x57, 0x2c, 0x94, 0x84, 0x42, 0xf1, 0xe8, 0xfb, 0xc3, 0xd2, 0xd1, 0x5e, 0xf1, 0xa0, + 0x90, 0xdf, 0x79, 0xba, 0x53, 0xf8, 0x26, 0xea, 0x63, 0xe7, 0x4e, 0xcf, 0x53, 0x11, 0x9b, 0x89, + 0xe1, 0x61, 0x61, 0x74, 0xc4, 0xde, 0xfe, 0xfe, 0x41, 0xd4, 0xcf, 0x4e, 0x9d, 0x9e, 0xa7, 0x42, + 0x7a, 0x9b, 0x49, 0xc3, 0xf2, 0x28, 0xa6, 0x78, 0x94, 0xcf, 0x17, 0x8a, 0xc5, 0x68, 0x80, 0x8d, + 0x9c, 0x9e, 0xa7, 0x26, 0x69, 0x97, 0x0d, 0xbd, 0xfc, 0x2d, 0xe9, 0xdb, 0x78, 0x33, 0x05, 0xc1, + 0x5d, 0xb5, 0xc6, 0xd4, 0x61, 0x6e, 0xf4, 0xa9, 0xee, 0xbc, 0xdc, 0xf1, 0x07, 0x33, 0x9b, 0xf5, + 0x08, 0xb4, 0x88, 0x3d, 0x81, 0xd9, 0x91, 0xf7, 0xf1, 0x03, 0x0f, 0x21, 0x0e, 0x95, 0x2e, 0x9b, + 0xf1, 0x86, 0x73, 0x99, 0x49, 0xbf, 0x02, 0x7b, 0x99, 0x69, 0x5b, 0xac, 0x7b, 0x9a, 0xc9, 0xf6, + 0x14, 0x60, 0x34, 0x60, 0x1c, 0x9e, 0x01, 0xab, 0x1e, 0xa2, 0x50, 0x2c, 0xbb, 0xe1, 0x1d, 0x6b, + 0xcd, 0x2a, 0x43, 0x74, 0xec, 0xb6, 0x9c, 0xbe, 0x22, 0x8e, 0x85, 0x64, 0xd7, 0xbc, 0x22, 0xad, + 0xf9, 0x9e, 0x43, 0xcc, 0xf1, 0x86, 0xeb, 0x25, 0x90, 0xb9, 0xce, 0xcd, 0x6b, 0x80, 0xad, 0x89, + 0x7f, 0x02, 0xb0, 0x5d, 0x03, 0x79, 0xb7, 0x10, 0x03, 0x0c, 0xbb, 0x7a, 0x35, 0xc6, 0x8a, 0x5e, + 0x84, 0x49, 0xf3, 0xc6, 0xc3, 0xb9, 0x0d, 0xa3, 0x00, 0xf6, 0xe1, 0x15, 0x00, 0x7b, 0xed, 0x8d, + 0x1c, 0xc6, 0x0f, 0xae, 0x18, 0x4a, 0x71, 0xee, 0xb5, 0xe7, 0x72, 0x80, 0xd4, 0x61, 0x6e, 0x74, + 0xd7, 0x77, 0xcd, 0x72, 0x04, 0xe8, 0xfe, 0xf1, 0xba, 0xec, 0x8a, 0xb9, 0xe2, 0xdb, 0x8b, 0xa4, + 0xff, 0xdd, 0x45, 0xd2, 0xff, 0xef, 0x45, 0xd2, 0xff, 0xea, 0x32, 0xe9, 0x7b, 0x77, 0x99, 0xf4, + 0xfd, 0x7d, 0x99, 0xf4, 0x1d, 0x3f, 0xa9, 0x49, 0xda, 0x49, 0xbb, 0x92, 0x11, 0x71, 0x33, 0x2b, + 0x62, 0xb5, 0x89, 0xd5, 0xac, 0x54, 0x11, 0x1f, 0xd5, 0x70, 0xb6, 0xb3, 0x99, 0x6d, 0xe2, 0x6a, + 0xbb, 0x81, 0x54, 0xe3, 0xaf, 0xe1, 0xda, 0xe3, 0x47, 0xe6, 0x8f, 0x43, 0xad, 0xdb, 0x42, 0x6a, + 0x25, 0x4c, 0x7e, 0x1a, 0x6e, 0xfe, 0x1f, 0x00, 0x00, 0xff, 0xff, 0xd5, 0x99, 0xe1, 0x0c, 0xc3, + 0x14, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1954,6 +1999,11 @@ func (m *MsgRecvPacketResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = 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 } @@ -2039,6 +2089,11 @@ func (m *MsgTimeoutResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = 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 } @@ -2131,6 +2186,11 @@ func (m *MsgTimeoutOnCloseResponse) MarshalToSizedBuffer(dAtA []byte) (int, erro _ = 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 } @@ -2218,6 +2278,11 @@ func (m *MsgAcknowledgementResponse) MarshalToSizedBuffer(dAtA []byte) (int, err _ = 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 } @@ -2479,6 +2544,9 @@ func (m *MsgRecvPacketResponse) Size() (n int) { } var l int _ = l + if m.Result != 0 { + n += 1 + sovTx(uint64(m.Result)) + } return n } @@ -2512,6 +2580,9 @@ func (m *MsgTimeoutResponse) Size() (n int) { } var l int _ = l + if m.Result != 0 { + n += 1 + sovTx(uint64(m.Result)) + } return n } @@ -2549,6 +2620,9 @@ func (m *MsgTimeoutOnCloseResponse) Size() (n int) { } var l int _ = l + if m.Result != 0 { + n += 1 + sovTx(uint64(m.Result)) + } return n } @@ -2583,6 +2657,9 @@ func (m *MsgAcknowledgementResponse) Size() (n int) { } var l int _ = l + if m.Result != 0 { + n += 1 + sovTx(uint64(m.Result)) + } return n } @@ -4409,6 +4486,25 @@ func (m *MsgRecvPacketResponse) Unmarshal(dAtA []byte) error { 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:]) @@ -4660,6 +4756,25 @@ func (m *MsgTimeoutResponse) Unmarshal(dAtA []byte) error { 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:]) @@ -4945,6 +5060,25 @@ func (m *MsgTimeoutOnCloseResponse) Unmarshal(dAtA []byte) error { 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:]) @@ -5211,6 +5345,25 @@ func (m *MsgAcknowledgementResponse) Unmarshal(dAtA []byte) error { 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:]) diff --git a/modules/core/ante/ante.go b/modules/core/ante/ante.go index 42e392905ec..e9218ea4b94 100644 --- a/modules/core/ante/ante.go +++ b/modules/core/ante/ante.go @@ -4,22 +4,23 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - channelkeeper "github.com/cosmos/ibc-go/v3/modules/core/04-channel/keeper" channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v3/modules/core/keeper" ) 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} } -// AnteDecorator 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. +// AnteDecorator 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 (ad AnteDecorator) 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 { @@ -29,31 +30,50 @@ func (ad AnteDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, ne for _, m := range tx.GetMsgs() { switch msg := m.(type) { case *channeltypes.MsgRecvPacket: - if _, found := ad.k.GetPacketReceipt(ctx, msg.Packet.GetDestPort(), msg.Packet.GetDestChannel(), msg.Packet.GetSequence()); found { + response, err := ad.k.RecvPacket(sdk.WrapSDKContext(ctx), msg) + if err != nil { + return ctx, err + } + if response.Result == channeltypes.NOOP { redundancies += 1 } packetMsgs += 1 case *channeltypes.MsgAcknowledgement: - if commitment := ad.k.GetPacketCommitment(ctx, msg.Packet.GetSourcePort(), msg.Packet.GetSourceChannel(), msg.Packet.GetSequence()); len(commitment) == 0 { + response, err := ad.k.Acknowledgement(sdk.WrapSDKContext(ctx), msg) + if err != nil { + return ctx, err + } + if response.Result == channeltypes.NOOP { redundancies += 1 } packetMsgs += 1 case *channeltypes.MsgTimeout: - if commitment := ad.k.GetPacketCommitment(ctx, msg.Packet.GetSourcePort(), msg.Packet.GetSourceChannel(), msg.Packet.GetSequence()); len(commitment) == 0 { + response, err := ad.k.Timeout(sdk.WrapSDKContext(ctx), msg) + if err != nil { + return ctx, err + } + if response.Result == channeltypes.NOOP { redundancies += 1 } packetMsgs += 1 case *channeltypes.MsgTimeoutOnClose: - if commitment := ad.k.GetPacketCommitment(ctx, msg.Packet.GetSourcePort(), msg.Packet.GetSourceChannel(), msg.Packet.GetSequence()); len(commitment) == 0 { + response, err := ad.k.TimeoutOnClose(sdk.WrapSDKContext(ctx), msg) + if err != nil { + return ctx, err + } + if response.Result == channeltypes.NOOP { redundancies += 1 } packetMsgs += 1 case *clienttypes.MsgUpdateClient: - // do nothing here, as we want to avoid updating clients if it is batched with only redundant messages + _, err := ad.k.UpdateClient(sdk.WrapSDKContext(ctx), msg) + if err != nil { + return ctx, err + } default: // if the multiMsg tx has a msg that is not a packet msg or update msg, then we will not return error @@ -61,7 +81,6 @@ func (ad AnteDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, ne // even if they get batched with redundant packet messages. return next(ctx, tx, simulate) } - } // only return error if all packet messages are redundant diff --git a/modules/core/ante/ante_test.go b/modules/core/ante/ante_test.go index 0336e07a3ae..c04f6483f74 100644 --- a/modules/core/ante/ante_test.go +++ b/modules/core/ante/ante_test.go @@ -4,13 +4,15 @@ import ( "testing" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v3/modules/core/24-host" "github.com/cosmos/ibc-go/v3/modules/core/ante" + "github.com/cosmos/ibc-go/v3/modules/core/exported" ibctesting "github.com/cosmos/ibc-go/v3/testing" - "github.com/cosmos/ibc-go/v3/testing/mock" ) type AnteTestSuite struct { @@ -42,6 +44,133 @@ func TestAnteTestSuite(t *testing.T) { suite.Run(t, new(AnteTestSuite)) } +// createRecvPacketMessage creates a RecvPacket message for a packet sent from chain A to chain B. +func (suite *AnteTestSuite) createRecvPacketMessage(sequenceNumber uint64, isRedundant bool) sdk.Msg { + packet := channeltypes.NewPacket(ibctesting.MockPacketData, sequenceNumber, + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + clienttypes.NewHeight(1, 0), 0) + + err := suite.path.EndpointA.SendPacket(packet) + suite.Require().NoError(err) + + 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()) +} + +// createAcknowledgementMessage creates an Acknowledgement message for a packet sent from chain B to chain A. +func (suite *AnteTestSuite) createAcknowledgementMessage(sequenceNumber uint64, isRedundant bool) sdk.Msg { + packet := channeltypes.NewPacket(ibctesting.MockPacketData, sequenceNumber, + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + clienttypes.NewHeight(1, 0), 0) + + err := suite.path.EndpointB.SendPacket(packet) + suite.Require().NoError(err) + 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.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + proof, proofHeight := suite.chainA.QueryProof(packetKey) + + return channeltypes.NewMsgAcknowledgement(packet, ibctesting.MockAcknowledgement, 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(sequenceNumber uint64, isRedundant bool) sdk.Msg { + height := suite.chainA.LastHeader.GetHeight() + timeoutHeight := clienttypes.NewHeight(height.GetRevisionNumber(), height.GetRevisionHeight()+1) + packet := channeltypes.NewPacket(ibctesting.MockPacketData, sequenceNumber, + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + timeoutHeight, 0) + + err := suite.path.EndpointB.SendPacket(packet) + suite.Require().NoError(err) + + suite.coordinator.CommitNBlocks(suite.chainA, 3) + + err = suite.path.EndpointB.UpdateClient() + suite.Require().NoError(err) + + 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, sequenceNumber, 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(sequenceNumber uint64, isRedundant bool) sdk.Msg { + height := suite.chainA.LastHeader.GetHeight() + timeoutHeight := clienttypes.NewHeight(height.GetRevisionNumber(), height.GetRevisionHeight()+1) + packet := channeltypes.NewPacket(ibctesting.MockPacketData, sequenceNumber, + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + timeoutHeight, 0) + + err := suite.path.EndpointB.SendPacket(packet) + suite.Require().NoError(err) + err = suite.path.EndpointA.SetChannelClosed() + suite.Require().NoError(err) + + 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()) + proofClosed, _ := suite.chainA.QueryProof(channelKey) + + return channeltypes.NewMsgTimeoutOnClose(packet, 1, proof, proofClosed, 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.Header + + switch endpoint.ClientConfig.GetClientType() { + case exported.Tendermint: + header, _ = endpoint.Chain.ConstructUpdateTMClientHeader(endpoint.Counterparty.Chain, endpoint.ClientID) + + default: + } + + msg, err := clienttypes.NewMsgUpdateClient( + endpoint.ClientID, header, + endpoint.Chain.SenderAccount.GetAddress().String(), + ) + require.NoError(endpoint.Chain.T, err) + + return msg +} + func (suite *AnteTestSuite) TestAnteDecorator() { testCases := []struct { name string @@ -49,404 +178,276 @@ func (suite *AnteTestSuite) TestAnteDecorator() { expPass bool }{ { - "success on single msg", + "success on one new RecvPacket message", func(suite *AnteTestSuite) []sdk.Msg { - packet := channeltypes.NewPacket([]byte(mock.MockPacketData), 1, - suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, - suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, - clienttypes.NewHeight(1, 0), 0) - - return []sdk.Msg{channeltypes.NewMsgRecvPacket(packet, []byte("proof"), clienttypes.NewHeight(0, 1), "signer")} + // the RecvPacket message has not been submitted to the chain yet, so it will succeed + return []sdk.Msg{suite.createRecvPacketMessage(1, false)} }, true, }, { - "success on multiple msgs", + "success on one new Acknowledgement message", func(suite *AnteTestSuite) []sdk.Msg { - var msgs []sdk.Msg - - for i := 1; i <= 5; i++ { - packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), - suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, - suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, - clienttypes.NewHeight(1, 0), 0) - - msgs = append(msgs, channeltypes.NewMsgRecvPacket(packet, []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) - } - return msgs + // the Acknowledgement message has not been submitted to the chain yet, so it will succeed + return []sdk.Msg{suite.createAcknowledgementMessage(1, false)} }, true, }, { - "success on multiple msgs: 1 fresh recv packet", + "success on one new Timeout message", func(suite *AnteTestSuite) []sdk.Msg { - var msgs []sdk.Msg - - for i := 1; i <= 5; i++ { - packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), - suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, - suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, - clienttypes.NewHeight(1, 0), 0) - - err := suite.path.EndpointA.SendPacket(packet) - suite.Require().NoError(err) - - // receive all sequences except packet 3 - if i != 3 { - err = suite.path.EndpointB.RecvPacket(packet) - suite.Require().NoError(err) - } - - msgs = append(msgs, channeltypes.NewMsgRecvPacket(packet, []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) - } - - return msgs + // the Timeout message has not been submitted to the chain yet, so it will succeed + return []sdk.Msg{suite.createTimeoutMessage(1, false)} }, true, }, { - "success on multiple mixed msgs", + "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(uint64(1), false)} + }, + true, + }, + { + "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++ { - packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), - suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, - suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, - clienttypes.NewHeight(1, 0), 0) - err := suite.path.EndpointA.SendPacket(packet) - suite.Require().NoError(err) - - msgs = append(msgs, channeltypes.NewMsgRecvPacket(packet, []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) - } - for i := 1; i <= 3; i++ { - packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), - suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, - suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, - clienttypes.NewHeight(1, 0), 0) - err := suite.path.EndpointB.SendPacket(packet) - suite.Require().NoError(err) - - msgs = append(msgs, channeltypes.NewMsgAcknowledgement(packet, []byte("ack"), []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + msgs = append(msgs, suite.createRecvPacketMessage(uint64(i), false)) } - for i := 4; i <= 6; i++ { - packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), - suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, - suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, - clienttypes.NewHeight(1, 0), 0) - err := suite.path.EndpointB.SendPacket(packet) - suite.Require().NoError(err) - - msgs = append(msgs, channeltypes.NewMsgTimeout(packet, uint64(i), []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + + // from B to A + for i := 1; i <= 9; i++ { + switch { + case i >= 1 && i <= 3: + msgs = append(msgs, suite.createAcknowledgementMessage(uint64(i), false)) + case i >= 4 && i <= 6: + msgs = append(msgs, suite.createTimeoutMessage(uint64(i), false)) + case i >= 7 && i <= 9: + msgs = append(msgs, suite.createTimeoutOnCloseMessage(uint64(i), false)) + } } return msgs }, true, }, { - "success on multiple mixed msgs: 1 fresh packet of each type", + "success on three redundant messages of RecvPacket, Acknowledgement and TimeoutOnClose, and one new Timeout message", func(suite *AnteTestSuite) []sdk.Msg { var msgs []sdk.Msg - for i := 1; i <= 3; i++ { - packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), - suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, - suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, - clienttypes.NewHeight(1, 0), 0) - err := suite.path.EndpointA.SendPacket(packet) - suite.Require().NoError(err) - - // receive all sequences except packet 3 - if i != 3 { - - err := suite.path.EndpointB.RecvPacket(packet) - suite.Require().NoError(err) - } + // 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. - msgs = append(msgs, channeltypes.NewMsgRecvPacket(packet, []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) - } + // from A to B for i := 1; i <= 3; i++ { - packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), - suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, - suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, - clienttypes.NewHeight(1, 0), 0) - err := suite.path.EndpointB.SendPacket(packet) - suite.Require().NoError(err) - - // receive all acks except ack 2 - if i != 2 { - err = suite.path.EndpointA.RecvPacket(packet) - suite.Require().NoError(err) - err = suite.path.EndpointB.AcknowledgePacket(packet, mock.MockAcknowledgement.Acknowledgement()) - suite.Require().NoError(err) - } - - msgs = append(msgs, channeltypes.NewMsgAcknowledgement(packet, []byte("ack"), []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + msgs = append(msgs, suite.createRecvPacketMessage(uint64(i), true)) } - for i := 4; i <= 6; i++ { - height := suite.chainA.LastHeader.GetHeight() - timeoutHeight := clienttypes.NewHeight(height.GetRevisionNumber(), height.GetRevisionHeight()+1) - packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), - suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, - suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, - timeoutHeight, 0) - err := suite.path.EndpointB.SendPacket(packet) - suite.Require().NoError(err) - - // timeout packet - suite.coordinator.CommitNBlocks(suite.chainA, 3) - - // timeout packets except sequence 5 - if i != 5 { - suite.path.EndpointB.UpdateClient() - err = suite.path.EndpointB.TimeoutPacket(packet) - suite.Require().NoError(err) - } - msgs = append(msgs, channeltypes.NewMsgTimeout(packet, uint64(i), []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + // from B to A + for i := 1; i <= 7; i++ { + switch { + case i >= 1 && i <= 3: + msgs = append(msgs, suite.createAcknowledgementMessage(uint64(i), true)) + case i == 4: + msgs = append(msgs, suite.createTimeoutMessage(uint64(i), false)) + case i >= 5 && i <= 7: + msgs = append(msgs, suite.createTimeoutOnCloseMessage(uint64(i), true)) + } } return msgs }, true, }, { - "success on multiple mixed msgs: only 1 fresh msg in total", + "success on one new message and two redundant messages of each type", func(suite *AnteTestSuite) []sdk.Msg { var msgs []sdk.Msg - for i := 1; i <= 3; i++ { - packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), - suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, - suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, - clienttypes.NewHeight(1, 0), 0) - - // receive all packets - suite.path.EndpointA.SendPacket(packet) - suite.path.EndpointB.RecvPacket(packet) + // 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. - msgs = append(msgs, channeltypes.NewMsgRecvPacket(packet, []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) - } + // from A to B for i := 1; i <= 3; i++ { - packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), - suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, - suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, - clienttypes.NewHeight(1, 0), 0) - - // receive all acks - suite.path.EndpointB.SendPacket(packet) - suite.path.EndpointA.RecvPacket(packet) - suite.path.EndpointB.AcknowledgePacket(packet, mock.MockAcknowledgement.Acknowledgement()) - - msgs = append(msgs, channeltypes.NewMsgAcknowledgement(packet, []byte("ack"), []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + msgs = append(msgs, suite.createRecvPacketMessage(uint64(i), i != 2)) } - for i := 4; i < 5; i++ { - height := suite.chainA.LastHeader.GetHeight() - timeoutHeight := clienttypes.NewHeight(height.GetRevisionNumber(), height.GetRevisionHeight()+1) - packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), - suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, - suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, - timeoutHeight, 0) - - // do not timeout packet, timeout msg is fresh - suite.path.EndpointB.SendPacket(packet) - - msgs = append(msgs, channeltypes.NewMsgTimeout(packet, uint64(i), []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + + // from B to A + for i := 1; i <= 9; i++ { + switch { + case i >= 1 && i <= 3: + msgs = append(msgs, suite.createAcknowledgementMessage(uint64(i), i != 2)) + case i >= 4 && i <= 6: + msgs = append(msgs, suite.createTimeoutMessage(uint64(i), i != 5)) + case i >= 7 && i <= 9: + msgs = append(msgs, suite.createTimeoutOnCloseMessage(uint64(i), i != 8)) + } } return msgs }, true, }, { - "success on single update client msg", + "success on one new UpdateClient message", func(suite *AnteTestSuite) []sdk.Msg { - return []sdk.Msg{&clienttypes.MsgUpdateClient{}} + return []sdk.Msg{suite.createUpdateClientMessage()} }, true, }, { - "success on multiple update clients", + "success on three new UpdateClient messages", func(suite *AnteTestSuite) []sdk.Msg { - return []sdk.Msg{&clienttypes.MsgUpdateClient{}, &clienttypes.MsgUpdateClient{}, &clienttypes.MsgUpdateClient{}} + return []sdk.Msg{suite.createUpdateClientMessage(), suite.createUpdateClientMessage(), suite.createUpdateClientMessage()} }, true, }, { - "success on multiple update clients and fresh packet message", + "success on three new Updateclient messages and one new RecvPacket message", func(suite *AnteTestSuite) []sdk.Msg { - msgs := []sdk.Msg{&clienttypes.MsgUpdateClient{}, &clienttypes.MsgUpdateClient{}, &clienttypes.MsgUpdateClient{}} - - packet := channeltypes.NewPacket([]byte(mock.MockPacketData), 1, - suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, - suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, - clienttypes.NewHeight(1, 0), 0) - - return append(msgs, channeltypes.NewMsgRecvPacket(packet, []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + return []sdk.Msg{ + suite.createUpdateClientMessage(), + suite.createUpdateClientMessage(), + suite.createUpdateClientMessage(), + suite.createRecvPacketMessage(uint64(1), false), + } }, true, }, { - "success of tx with different msg type even if all packet messages are redundant", + "success on three redundant RecvPacket messages and one SubmitMisbehaviour message", func(suite *AnteTestSuite) []sdk.Msg { - msgs := []sdk.Msg{&clienttypes.MsgUpdateClient{}} + msgs := []sdk.Msg{suite.createUpdateClientMessage()} for i := 1; i <= 3; i++ { - packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), - suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, - suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, - clienttypes.NewHeight(1, 0), 0) - - // receive all packets - suite.path.EndpointA.SendPacket(packet) - suite.path.EndpointB.RecvPacket(packet) - - msgs = append(msgs, channeltypes.NewMsgRecvPacket(packet, []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) - } - for i := 1; i <= 3; i++ { - packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), - suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, - suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, - clienttypes.NewHeight(1, 0), 0) - - // receive all acks - suite.path.EndpointB.SendPacket(packet) - suite.path.EndpointA.RecvPacket(packet) - suite.path.EndpointB.AcknowledgePacket(packet, mock.MockAcknowledgement.Acknowledgement()) - - msgs = append(msgs, channeltypes.NewMsgAcknowledgement(packet, []byte("ack"), []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) - } - for i := 4; i < 6; i++ { - height := suite.chainA.LastHeader.GetHeight() - timeoutHeight := clienttypes.NewHeight(height.GetRevisionNumber(), height.GetRevisionHeight()+1) - packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), - suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, - suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, - timeoutHeight, 0) - - err := suite.path.EndpointB.SendPacket(packet) - suite.Require().NoError(err) - - // timeout packet - suite.coordinator.CommitNBlocks(suite.chainA, 3) - - suite.path.EndpointB.UpdateClient() - suite.path.EndpointB.TimeoutPacket(packet) - - msgs = append(msgs, channeltypes.NewMsgTimeoutOnClose(packet, uint64(i), []byte("proof"), []byte("channelProof"), clienttypes.NewHeight(0, 1), "signer")) + msgs = append(msgs, suite.createRecvPacketMessage(uint64(i), true)) } // append non packet and update message to msgs to ensure multimsg tx should pass msgs = append(msgs, &clienttypes.MsgSubmitMisbehaviour{}) - return msgs }, true, }, { - "no success on multiple mixed message: all are redundant", + "no success on one redundant RecvPacket message", + func(suite *AnteTestSuite) []sdk.Msg { + return []sdk.Msg{suite.createRecvPacketMessage(uint64(1), true)} + }, + false, + }, + { + "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++ { - packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), - suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, - suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, - clienttypes.NewHeight(1, 0), 0) - - // receive all packets - suite.path.EndpointA.SendPacket(packet) - suite.path.EndpointB.RecvPacket(packet) - - msgs = append(msgs, channeltypes.NewMsgRecvPacket(packet, []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + msgs = append(msgs, suite.createRecvPacketMessage(uint64(i), true)) } - for i := 1; i <= 3; i++ { - packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), - suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, - suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, - clienttypes.NewHeight(1, 0), 0) - // receive all acks - suite.path.EndpointB.SendPacket(packet) - suite.path.EndpointA.RecvPacket(packet) - suite.path.EndpointB.AcknowledgePacket(packet, mock.MockAcknowledgement.Acknowledgement()) - - msgs = append(msgs, channeltypes.NewMsgAcknowledgement(packet, []byte("ack"), []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) - } - for i := 4; i < 6; i++ { - height := suite.chainA.LastHeader.GetHeight() - timeoutHeight := clienttypes.NewHeight(height.GetRevisionNumber(), height.GetRevisionHeight()+1) - packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), - suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, - suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, - timeoutHeight, 0) - - err := suite.path.EndpointB.SendPacket(packet) - suite.Require().NoError(err) - - // timeout packet - suite.coordinator.CommitNBlocks(suite.chainA, 3) - - suite.path.EndpointB.UpdateClient() - suite.path.EndpointB.TimeoutPacket(packet) - - msgs = append(msgs, channeltypes.NewMsgTimeoutOnClose(packet, uint64(i), []byte("proof"), []byte("channelProof"), clienttypes.NewHeight(0, 1), "signer")) + // from B to A + for i := 1; i <= 9; i++ { + switch { + case i >= 1 && i <= 3: + msgs = append(msgs, suite.createAcknowledgementMessage(uint64(i), true)) + case i >= 4 && i <= 6: + msgs = append(msgs, suite.createTimeoutMessage(uint64(i), true)) + case i >= 7 && i <= 9: + msgs = append(msgs, suite.createTimeoutOnCloseMessage(uint64(i), true)) + } } return msgs }, false, }, { - "no success if msgs contain update clients and redundant packet messages", + "no success on one new UpdateClient message and three redundant RecvPacket messages", func(suite *AnteTestSuite) []sdk.Msg { - msgs := []sdk.Msg{&clienttypes.MsgUpdateClient{}, &clienttypes.MsgUpdateClient{}, &clienttypes.MsgUpdateClient{}} + msgs := []sdk.Msg{&clienttypes.MsgUpdateClient{}} for i := 1; i <= 3; i++ { - packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), - suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, - suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, - clienttypes.NewHeight(1, 0), 0) - - // receive all packets - suite.path.EndpointA.SendPacket(packet) - suite.path.EndpointB.RecvPacket(packet) - - msgs = append(msgs, channeltypes.NewMsgRecvPacket(packet, []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + msgs = append(msgs, suite.createRecvPacketMessage(uint64(i), true)) } - for i := 1; i <= 3; i++ { - packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), - suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, - suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, - clienttypes.NewHeight(1, 0), 0) - // receive all acks - suite.path.EndpointB.SendPacket(packet) - suite.path.EndpointA.RecvPacket(packet) - suite.path.EndpointB.AcknowledgePacket(packet, mock.MockAcknowledgement.Acknowledgement()) + return msgs + }, + false, + }, + { + "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()} - msgs = append(msgs, channeltypes.NewMsgAcknowledgement(packet, []byte("ack"), []byte("proof"), clienttypes.NewHeight(0, 1), "signer")) + // from A to B + for i := 1; i <= 3; i++ { + msgs = append(msgs, suite.createRecvPacketMessage(uint64(i), true)) } - for i := 4; i < 6; i++ { - height := suite.chainA.LastHeader.GetHeight() - timeoutHeight := clienttypes.NewHeight(height.GetRevisionNumber(), height.GetRevisionHeight()+1) - packet := channeltypes.NewPacket([]byte(mock.MockPacketData), uint64(i), - suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, - suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, - timeoutHeight, 0) - - err := suite.path.EndpointB.SendPacket(packet) - suite.Require().NoError(err) - - // timeout packet - suite.coordinator.CommitNBlocks(suite.chainA, 3) - suite.path.EndpointB.UpdateClient() - suite.path.EndpointB.TimeoutPacket(packet) - - msgs = append(msgs, channeltypes.NewMsgTimeoutOnClose(packet, uint64(i), []byte("proof"), []byte("channelProof"), clienttypes.NewHeight(0, 1), "signer")) + // from B to A + for i := 1; i <= 9; i++ { + switch { + case i >= 1 && i <= 3: + msgs = append(msgs, suite.createAcknowledgementMessage(uint64(i), true)) + case i >= 4 && i <= 6: + msgs = append(msgs, suite.createTimeoutMessage(uint64(i), true)) + case i >= 7 && i <= 9: + msgs = append(msgs, suite.createTimeoutOnCloseMessage(uint64(i), true)) + } } return msgs }, false, }, + { + "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(1, 0), 0) + + return []sdk.Msg{ + suite.createRecvPacketMessage(uint64(1), false), + channeltypes.NewMsgRecvPacket(packet, []byte("proof"), clienttypes.NewHeight(0, 1), "signer"), + } + }, + false, + }, + { + "no success on one new message and one redundant message in the same block", + func(suite *AnteTestSuite) []sdk.Msg { + msg := suite.createRecvPacketMessage(uint64(1), false) + + // We want to be able to run check tx with the non-redundant message without + // commiting 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.NewAnteDecorator(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} + }, + false, + }, } for _, tc := range testCases { @@ -456,7 +457,7 @@ func (suite *AnteTestSuite) TestAnteDecorator() { // reset suite suite.SetupTest() - k := suite.chainB.App.GetIBCKeeper().ChannelKeeper + k := suite.chainB.App.GetIBCKeeper() decorator := ante.NewAnteDecorator(k) msgs := tc.malleate(suite) diff --git a/modules/core/keeper/msg_server.go b/modules/core/keeper/msg_server.go index a380540614c..be18391c698 100644 --- a/modules/core/keeper/msg_server.go +++ b/modules/core/keeper/msg_server.go @@ -395,7 +395,7 @@ func (k Keeper) RecvPacket(goCtx context.Context, msg *channeltypes.MsgRecvPacke case nil: writeFn() case channeltypes.ErrNoOpMsg: - return &channeltypes.MsgRecvPacketResponse{}, nil // no-op + return &channeltypes.MsgRecvPacketResponse{Result: channeltypes.NOOP}, nil default: return nil, sdkerrors.Wrap(err, "receive packet verification failed") } @@ -435,7 +435,7 @@ func (k Keeper) RecvPacket(goCtx context.Context, msg *channeltypes.MsgRecvPacke ) }() - return &channeltypes.MsgRecvPacketResponse{}, nil + return &channeltypes.MsgRecvPacketResponse{Result: channeltypes.SUCCESS}, nil } // Timeout defines a rpc handler method for MsgTimeout. @@ -473,7 +473,7 @@ func (k Keeper) Timeout(goCtx context.Context, msg *channeltypes.MsgTimeout) (*c case nil: writeFn() case channeltypes.ErrNoOpMsg: - return &channeltypes.MsgTimeoutResponse{}, nil // no-op + return &channeltypes.MsgTimeoutResponse{Result: channeltypes.NOOP}, nil default: return nil, sdkerrors.Wrap(err, "timeout packet verification failed") } @@ -503,7 +503,7 @@ func (k Keeper) Timeout(goCtx context.Context, msg *channeltypes.MsgTimeout) (*c ) }() - return &channeltypes.MsgTimeoutResponse{}, nil + return &channeltypes.MsgTimeoutResponse{Result: channeltypes.SUCCESS}, nil } // TimeoutOnClose defines a rpc handler method for MsgTimeoutOnClose. @@ -541,7 +541,7 @@ func (k Keeper) TimeoutOnClose(goCtx context.Context, msg *channeltypes.MsgTimeo case nil: writeFn() case channeltypes.ErrNoOpMsg: - return &channeltypes.MsgTimeoutOnCloseResponse{}, nil // no-op + return &channeltypes.MsgTimeoutOnCloseResponse{Result: channeltypes.NOOP}, nil default: return nil, sdkerrors.Wrap(err, "timeout on close packet verification failed") } @@ -574,7 +574,7 @@ func (k Keeper) TimeoutOnClose(goCtx context.Context, msg *channeltypes.MsgTimeo ) }() - return &channeltypes.MsgTimeoutOnCloseResponse{}, nil + return &channeltypes.MsgTimeoutOnCloseResponse{Result: channeltypes.SUCCESS}, nil } // Acknowledgement defines a rpc handler method for MsgAcknowledgement. @@ -612,7 +612,7 @@ func (k Keeper) Acknowledgement(goCtx context.Context, msg *channeltypes.MsgAckn case nil: writeFn() case channeltypes.ErrNoOpMsg: - return &channeltypes.MsgAcknowledgementResponse{}, nil // no-op + return &channeltypes.MsgAcknowledgementResponse{Result: channeltypes.NOOP}, nil default: return nil, sdkerrors.Wrap(err, "acknowledge packet verification failed") } @@ -636,5 +636,5 @@ func (k Keeper) Acknowledgement(goCtx context.Context, msg *channeltypes.MsgAckn ) }() - return &channeltypes.MsgAcknowledgementResponse{}, nil + return &channeltypes.MsgAcknowledgementResponse{Result: channeltypes.SUCCESS}, nil } diff --git a/proto/ibc/core/channel/v1/tx.proto b/proto/ibc/core/channel/v1/tx.proto index 7fc8f730e33..d4d6df1d5e9 100644 --- a/proto/ibc/core/channel/v1/tx.proto +++ b/proto/ibc/core/channel/v1/tx.proto @@ -42,6 +42,18 @@ service Msg { 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_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_NOOP = 1 [(gogoproto.enumvalue_customname) = "NOOP"]; + // The message was executed successfully + RESPONSE_RESULT_SUCCESS = 2 [(gogoproto.enumvalue_customname) = "SUCCESS"]; +} + // MsgChannelOpenInit defines an sdk.Msg to initialize a channel handshake. It // is called by a relayer on Chain A. message MsgChannelOpenInit { @@ -163,7 +175,11 @@ message MsgRecvPacket { } // MsgRecvPacketResponse defines the Msg/RecvPacket response type. -message MsgRecvPacketResponse {} +message MsgRecvPacketResponse { + option (gogoproto.goproto_getters) = false; + + ResponseResultType result = 1; +} // MsgTimeout receives timed-out packet message MsgTimeout { @@ -179,7 +195,11 @@ message MsgTimeout { } // MsgTimeoutResponse defines the Msg/Timeout response type. -message MsgTimeoutResponse {} +message MsgTimeoutResponse { + option (gogoproto.goproto_getters) = false; + + ResponseResultType result = 1; +} // MsgTimeoutOnClose timed-out packet upon counterparty channel closure. message MsgTimeoutOnClose { @@ -196,7 +216,11 @@ message MsgTimeoutOnClose { } // MsgTimeoutOnCloseResponse defines the Msg/TimeoutOnClose response type. -message MsgTimeoutOnCloseResponse {} +message MsgTimeoutOnCloseResponse { + option (gogoproto.goproto_getters) = false; + + ResponseResultType result = 1; +} // MsgAcknowledgement receives incoming IBC acknowledgement message MsgAcknowledgement { @@ -212,4 +236,8 @@ message MsgAcknowledgement { } // MsgAcknowledgementResponse defines the Msg/Acknowledgement response type. -message MsgAcknowledgementResponse {} +message MsgAcknowledgementResponse { + option (gogoproto.goproto_getters) = false; + + ResponseResultType result = 1; +} diff --git a/testing/endpoint.go b/testing/endpoint.go index 5c2e2e33f4f..513fc8923cf 100644 --- a/testing/endpoint.go +++ b/testing/endpoint.go @@ -462,6 +462,36 @@ func (endpoint *Endpoint) TimeoutPacket(packet channeltypes.Packet) error { return endpoint.Chain.sendMsgs(timeoutMsg) } +// 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()) + proofClosed, _ := endpoint.Counterparty.QueryProof(channelKey) + + nextSeqRecv, found := endpoint.Counterparty.Chain.App.GetIBCKeeper().ChannelKeeper.GetNextSequenceRecv(endpoint.Counterparty.Chain.GetContext(), endpoint.ChannelConfig.PortID, endpoint.ChannelID) + require.True(endpoint.Chain.T, found) + + timeoutOnCloseMsg := channeltypes.NewMsgTimeoutOnClose( + packet, nextSeqRecv, + proof, proofClosed, proofHeight, endpoint.Chain.SenderAccount.GetAddress().String(), + ) + + return endpoint.Chain.sendMsgs(timeoutOnCloseMsg) +} + // SetChannelClosed sets a channel state to CLOSED. func (endpoint *Endpoint) SetChannelClosed() error { channel := endpoint.GetChannel() diff --git a/testing/simapp/ante_handler.go b/testing/simapp/ante_handler.go index 8e3e1f069ec..04ffad13e2a 100644 --- a/testing/simapp/ante_handler.go +++ b/testing/simapp/ante_handler.go @@ -5,16 +5,15 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/auth/ante" - channelkeeper "github.com/cosmos/ibc-go/v3/modules/core/04-channel/keeper" ibcante "github.com/cosmos/ibc-go/v3/modules/core/ante" + "github.com/cosmos/ibc-go/v3/modules/core/keeper" ) -// HandlerOptions extend the SDK's AnteHandler options by requiring the IBC -// channel keeper. +// HandlerOptions extend the SDK's AnteHandler options by requiring the IBC keeper. type HandlerOptions struct { ante.HandlerOptions - IBCChannelkeeper channelkeeper.Keeper + IBCKeeper *keeper.Keeper } // NewAnteHandler creates a new ante handler @@ -49,7 +48,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { ante.NewSigGasConsumeDecorator(options.AccountKeeper, sigGasConsumer), ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler), ante.NewIncrementSequenceDecorator(options.AccountKeeper), - ibcante.NewAnteDecorator(options.IBCChannelkeeper), + ibcante.NewAnteDecorator(options.IBCKeeper), } return sdk.ChainAnteDecorators(anteDecorators...), nil diff --git a/testing/simapp/app.go b/testing/simapp/app.go index 15add30faaf..ee148806852 100644 --- a/testing/simapp/app.go +++ b/testing/simapp/app.go @@ -503,7 +503,7 @@ func NewSimApp( FeegrantKeeper: app.FeeGrantKeeper, SigGasConsumer: ante.DefaultSigVerificationGasConsumer, }, - IBCChannelkeeper: app.IBCKeeper.ChannelKeeper, + IBCKeeper: app.IBCKeeper, }, ) if err != nil { From 1f346a7219176a2827be4872562d30e35486d016 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 11 Mar 2022 11:16:13 +0000 Subject: [PATCH 054/140] ibctesting: custom voting power reduction for testing (#939) (#1104) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ibctesting: custom voting power reduction for testing * changelog * fix * make T public * fix * revert changes * fix test (cherry picked from commit c32e4bea2500bee1d2fab4b2ad4908563f7f8a1c) Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> --- CHANGELOG.md | 3 ++- go.sum | 4 ---- testing/app.go | 18 +++++++++++++----- testing/chain.go | 15 ++++++--------- testing/coordinator.go | 12 ++---------- testing/endpoint.go | 4 ++-- 6 files changed, 25 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0629d3ebf13..c6c0d120e3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,7 +44,8 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (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. diff --git a/go.sum b/go.sum index 9caa6a21997..5544d71eb5f 100644 --- a/go.sum +++ b/go.sum @@ -466,7 +466,6 @@ github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIv github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= -github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= @@ -801,7 +800,6 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= -github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa h1:0U2s5loxrTy6/VgfVoLuVLFJcURKLH49ie0zSch7gh4= github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= @@ -1313,7 +1311,6 @@ google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdr google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= -google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= 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= @@ -1426,7 +1423,6 @@ google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD 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.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.44.0 h1:weqSxi/TMs1SqFRMHCtBgXRs8k3X39QIDEZ0pRcttUg= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= diff --git a/testing/app.go b/testing/app.go index 320bb0f00a7..3c6a14ed453 100644 --- a/testing/app.go +++ b/testing/app.go @@ -58,8 +58,9 @@ func SetupTestingApp() (TestingApp, map[string]json.RawMessage) { // 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(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount, chainID string, balances ...banktypes.Balance) TestingApp { +func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount, chainID string, powerReduction sdk.Int, balances ...banktypes.Balance) TestingApp { app, genesisState := DefaultTestingAppInit() + // set genesis accounts authGenesis := authtypes.NewGenesisState(authtypes.DefaultParams(), genAccs) genesisState[authtypes.ModuleName] = app.AppCodec().MustMarshalJSON(authGenesis) @@ -67,7 +68,7 @@ func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs validators := make([]stakingtypes.Validator, 0, len(valSet.Validators)) delegations := make([]stakingtypes.Delegation, 0, len(valSet.Validators)) - bondAmt := sdk.NewInt(1000000) + bondAmt := sdk.TokensFromConsensusPower(1, powerReduction) for _, val := range valSet.Validators { pk, err := cryptocodec.FromTmPubKeyInterface(val.PubKey) @@ -87,21 +88,28 @@ func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs Commission: stakingtypes.NewCommission(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), MinSelfDelegation: sdk.ZeroInt(), } + validators = append(validators, validator) delegations = append(delegations, stakingtypes.NewDelegation(genAccs[0].GetAddress(), val.Address.Bytes(), sdk.OneDec())) } + // set validators and delegations + var stakingGenesis stakingtypes.GenesisState + app.AppCodec().MustUnmarshalJSON(genesisState[stakingtypes.ModuleName], &stakingGenesis) + + bondDenom := stakingGenesis.Params.BondDenom + totalSupply := sdk.NewCoins() // add bonded amount to bonded pool module account balances = append(balances, banktypes.Balance{ Address: authtypes.NewModuleAddress(stakingtypes.BondedPoolName).String(), - Coins: sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, bondAmt.Mul(sdk.NewInt(int64(len(valSet.Validators)))))}, + Coins: sdk.Coins{sdk.NewCoin(bondDenom, bondAmt.Mul(sdk.NewInt(int64(len(valSet.Validators)))))}, }) // set validators and delegations - stakingGenesis := stakingtypes.NewGenesisState(stakingtypes.DefaultParams(), validators, delegations) - genesisState[stakingtypes.ModuleName] = app.AppCodec().MustMarshalJSON(stakingGenesis) + stakingGenesis = *stakingtypes.NewGenesisState(stakingGenesis.Params, validators, delegations) + genesisState[stakingtypes.ModuleName] = app.AppCodec().MustMarshalJSON(&stakingGenesis) // update total supply bankGenesis := banktypes.NewGenesisState(banktypes.DefaultGenesisState().Params, balances, totalSupply, []banktypes.Metadata{}) diff --git a/testing/chain.go b/testing/chain.go index 4199595c1b9..28382a6c463 100644 --- a/testing/chain.go +++ b/testing/chain.go @@ -113,7 +113,7 @@ func NewTestChainWithValSet(t *testing.T, coord *Coordinator, chainID string, va senderAccs = append(senderAccs, senderAcc) } - app := SetupWithGenesisValSet(t, valSet, genAccs, chainID, genBals...) + app := SetupWithGenesisValSet(t, valSet, genAccs, chainID, sdk.DefaultPowerReduction, genBals...) // create current header and call begin block header := tmproto.Header{ @@ -453,6 +453,7 @@ func (chain *TestChain) CreateTMClientHeader(chainID string, blockHeight int64, EvidenceHash: tmhash.Sum([]byte("evidence_hash")), ProposerAddress: tmValSet.Proposer.Address, //nolint:staticcheck } + hhash := tmHeader.Hash() blockID := MakeBlockID(hhash, 3, tmhash.Sum([]byte("part_set"))) voteSet := tmtypes.NewVoteSet(chainID, blockHeight, 1, tmproto.PrecommitType, tmValSet) @@ -467,16 +468,12 @@ func (chain *TestChain) CreateTMClientHeader(chainID string, blockHeight int64, if tmValSet != nil { valSet, err = tmValSet.ToProto() - if err != nil { - panic(err) - } + require.NoError(chain.T, err) } if tmTrustedVals != nil { trustedVals, err = tmTrustedVals.ToProto() - if err != nil { - panic(err) - } + require.NoError(chain.T, err) } // The trusted fields may be nil. They may be filled before relaying messages to a client. @@ -505,8 +502,8 @@ func MakeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) tmtypes.Bl // sorting of ValidatorSet. // The sorting is first by .VotingPower (descending), with secondary index of .Address (ascending). func CreateSortedSignerArray(altPrivVal, suitePrivVal tmtypes.PrivValidator, - altVal, suiteVal *tmtypes.Validator) []tmtypes.PrivValidator { - + altVal, suiteVal *tmtypes.Validator, +) []tmtypes.PrivValidator { switch { case altVal.VotingPower > suiteVal.VotingPower: return []tmtypes.PrivValidator{altPrivVal, suitePrivVal} diff --git a/testing/coordinator.go b/testing/coordinator.go index 65bf5a4f1f3..217c257473a 100644 --- a/testing/coordinator.go +++ b/testing/coordinator.go @@ -212,11 +212,7 @@ func (coord *Coordinator) ConnOpenInitOnBothChains(path *Path) error { return err } - if err := path.EndpointB.UpdateClient(); err != nil { - return err - } - - return nil + return path.EndpointB.UpdateClient() } // ChanOpenInitOnBothChains initializes a channel on the source chain and counterparty chain @@ -237,9 +233,5 @@ func (coord *Coordinator) ChanOpenInitOnBothChains(path *Path) error { return err } - if err := path.EndpointB.UpdateClient(); err != nil { - return err - } - - return nil + return path.EndpointB.UpdateClient() } diff --git a/testing/endpoint.go b/testing/endpoint.go index 513fc8923cf..607f7a16843 100644 --- a/testing/endpoint.go +++ b/testing/endpoint.go @@ -67,7 +67,7 @@ func (endpoint *Endpoint) QueryProof(key []byte) ([]byte, clienttypes.Height) { } // QueryProofAtHeight queries proof associated with this endpoint using the proof height -// providied +// 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)) @@ -98,7 +98,7 @@ func (endpoint *Endpoint) CreateClient() (err error) { consensusState = endpoint.Counterparty.Chain.LastHeader.ConsensusState() case exported.Solomachine: // TODO - // solo := NewSolomachine(Chain.T, endpoint.Chain.Codec, clientID, "", 1) + // solo := NewSolomachine(endpoint.Chain.T, endpoint.Chain.Codec, clientID, "", 1) // clientState = solo.ClientState() // consensusState = solo.ConsensusState() From b3823d40c7a5d1a23f261b4ea29ba6358abb32d1 Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Tue, 15 Mar 2022 07:39:15 +0100 Subject: [PATCH 055/140] update changelog --- CHANGELOG.md | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6c0d120e3f..86b626ff049 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -93,6 +93,39 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (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.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.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.2) - 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 @@ -134,6 +167,39 @@ Ref: https://keepachangelog.com/en/1.0.0/ * [\#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.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.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 From eaf9d6702f2083f614d472788b0c1ae9a583a0a7 Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Tue, 15 Mar 2022 08:04:25 +0100 Subject: [PATCH 056/140] update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86b626ff049..3ec65990512 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -257,6 +257,12 @@ Ref: https://keepachangelog.com/en/1.0.0/ * [\#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 From 6e1b25a60ddffd2d993c1c82676f8e318ba49f31 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 15 Mar 2022 12:54:58 +0100 Subject: [PATCH 057/140] fix: adjust InitModule to account for empty controller and host keepers (#1120) (#1121) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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. - [x] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [x] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [x] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [x] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [x] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [x] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [x] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [x] Re-reviewed `Files changed` in the Github PR explorer - [x] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit 0a81a52adc8482aab8467b19023c2233e6cca968) Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> --- docs/migrations/v2-to-v3.md | 7 +- modules/apps/27-interchain-accounts/module.go | 15 ++-- .../27-interchain-accounts/module_test.go | 76 ++++++++++++++++--- 3 files changed, 82 insertions(+), 16 deletions(-) diff --git a/docs/migrations/v2-to-v3.md b/docs/migrations/v2-to-v3.md index 37224134b6b..275e9d7356d 100644 --- a/docs/migrations/v2-to-v3.md +++ b/docs/migrations/v2-to-v3.md @@ -64,8 +64,8 @@ app.UpgradeKeeper.SetUpgradeHandler("v3", ``` -The host and controller submodule params only need to be set if you integrate those submodules. -For example, if a chain chooses not to integrate a controller submodule, it does not need to set the controller params. +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 @@ -82,6 +82,9 @@ if upgradeInfo.Name == "v3" && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Heigh ``` 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 diff --git a/modules/apps/27-interchain-accounts/module.go b/modules/apps/27-interchain-accounts/module.go index 969c07caf9d..1bb870fca5d 100644 --- a/modules/apps/27-interchain-accounts/module.go +++ b/modules/apps/27-interchain-accounts/module.go @@ -105,12 +105,17 @@ func NewAppModule(controllerKeeper *controllerkeeper.Keeper, hostKeeper *hostkee // InitModule will initialize the interchain accounts moudule. It should only be // called once and as an alternative to InitGenesis. func (am AppModule) InitModule(ctx sdk.Context, controllerParams controllertypes.Params, hostParams hosttypes.Params) { - am.controllerKeeper.SetParams(ctx, controllerParams) - am.hostKeeper.SetParams(ctx, hostParams) + if am.controllerKeeper != nil { + am.controllerKeeper.SetParams(ctx, controllerParams) + } + + if am.hostKeeper != nil { + am.hostKeeper.SetParams(ctx, hostParams) - cap := am.hostKeeper.BindPort(ctx, types.PortID) - if err := am.hostKeeper.ClaimCapability(ctx, cap, ibchost.PortPath(types.PortID)); err != nil { - panic(fmt.Sprintf("could not claim port capability: %v", err)) + cap := am.hostKeeper.BindPort(ctx, types.PortID) + if err := am.hostKeeper.ClaimCapability(ctx, cap, ibchost.PortPath(types.PortID)); err != nil { + panic(fmt.Sprintf("could not claim port capability: %v", err)) + } } } diff --git a/modules/apps/27-interchain-accounts/module_test.go b/modules/apps/27-interchain-accounts/module_test.go index de5f51ae921..59517ab40e4 100644 --- a/modules/apps/27-interchain-accounts/module_test.go +++ b/modules/apps/27-interchain-accounts/module_test.go @@ -31,8 +31,9 @@ func (suite *InterchainAccountsTestSuite) SetupTest() { } func (suite *InterchainAccountsTestSuite) TestInitModule() { + // setup and basic testing app := simapp.NewSimApp(log.NewNopLogger(), dbm.NewMemDB(), nil, true, map[int64]bool{}, simapp.DefaultNodeHome, 5, simapp.MakeTestEncodingConfig(), simapp.EmptyAppOptions{}) - icamodule, ok := app.GetModuleManager().Modules[types.ModuleName].(ica.AppModule) + appModule, ok := app.GetModuleManager().Modules[types.ModuleName].(ica.AppModule) suite.Require().True(ok) header := tmproto.Header{ @@ -58,17 +59,74 @@ func (suite *InterchainAccountsTestSuite) TestInitModule() { expAllowMessages := []string{"sdk.Msg"} hostParams.HostEnabled = true hostParams.AllowMessages = expAllowMessages - suite.Require().False(app.IBCKeeper.PortKeeper.IsBound(ctx, types.PortID)) - icamodule.InitModule(ctx, controllerParams, hostParams) + testCases := []struct { + name string + malleate func() + expControllerPass bool + expHostPass bool + }{ + { + "both controller and host set", func() { + var ok bool + appModule, ok = app.GetModuleManager().Modules[types.ModuleName].(ica.AppModule) + suite.Require().True(ok) + }, true, true, + }, + { + "neither controller or host is set", func() { + appModule = ica.NewAppModule(nil, nil) + }, false, false, + }, + { + "only controller is set", func() { + appModule = ica.NewAppModule(&app.ICAControllerKeeper, nil) + }, true, false, + }, + { + "only host is set", func() { + appModule = ica.NewAppModule(nil, &app.ICAHostKeeper) + }, false, true, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + // reset app state + app = simapp.NewSimApp(log.NewNopLogger(), dbm.NewMemDB(), nil, true, map[int64]bool{}, simapp.DefaultNodeHome, 5, simapp.MakeTestEncodingConfig(), simapp.EmptyAppOptions{}) + header := tmproto.Header{ + ChainID: "testchain", + Height: 1, + Time: suite.coordinator.CurrentTime.UTC(), + } + + ctx := app.GetBaseApp().NewContext(true, header) - controllerParams = app.ICAControllerKeeper.GetParams(ctx) - suite.Require().True(controllerParams.ControllerEnabled) + tc.malleate() - hostParams = app.ICAHostKeeper.GetParams(ctx) - suite.Require().True(hostParams.HostEnabled) - suite.Require().Equal(expAllowMessages, hostParams.AllowMessages) + suite.Require().NotPanics(func() { + appModule.InitModule(ctx, controllerParams, hostParams) + }) + + if tc.expControllerPass { + controllerParams = app.ICAControllerKeeper.GetParams(ctx) + suite.Require().True(controllerParams.ControllerEnabled) + } + + if tc.expHostPass { + hostParams = app.ICAHostKeeper.GetParams(ctx) + suite.Require().True(hostParams.HostEnabled) + suite.Require().Equal(expAllowMessages, hostParams.AllowMessages) + + suite.Require().True(app.IBCKeeper.PortKeeper.IsBound(ctx, types.PortID)) + } + + }) + } - suite.Require().True(app.IBCKeeper.PortKeeper.IsBound(ctx, types.PortID)) } From fde4494d6768ead14173fe793223110d3d6bd123 Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Tue, 15 Mar 2022 14:03:29 +0100 Subject: [PATCH 058/140] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ec65990512..a744185c2ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ # Changelog -## [Unreleased] +## [v3.0.0](https://github.com/cosmos/ibc-go/releases/tag/v3.0.0) - 2022-03-15 ### Dependencies From 0ab85689060e1a338360acdba925b240a099c9d6 Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Tue, 15 Mar 2022 14:39:59 +0100 Subject: [PATCH 059/140] Merge pull request from GHSA-j658-c98j-fww4 (#1127) Co-authored-by: Carlos Rodriguez Co-authored-by: Carlos Rodriguez --- modules/apps/transfer/keeper/relay.go | 4 ++++ modules/apps/transfer/keeper/relay_test.go | 10 ++++++++++ modules/apps/transfer/types/expected_keepers.go | 1 + 3 files changed, 15 insertions(+) diff --git a/modules/apps/transfer/keeper/relay.go b/modules/apps/transfer/keeper/relay.go index ab7f3751588..3c3a5aa6690 100644 --- a/modules/apps/transfer/keeper/relay.go +++ b/modules/apps/transfer/keeper/relay.go @@ -239,6 +239,10 @@ func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, data t } token := sdk.NewCoin(denom, transferAmount) + if k.bankKeeper.BlockedAddr(receiver) { + return sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to receive funds", receiver) + } + // unescrow tokens escrowAddress := types.GetEscrowAddress(packet.GetDestPort(), packet.GetDestChannel()) if err := k.bankKeeper.SendCoins(ctx, escrowAddress, receiver, sdk.NewCoins(token)); err != nil { diff --git a/modules/apps/transfer/keeper/relay_test.go b/modules/apps/transfer/keeper/relay_test.go index 9d03bbde962..ce34f316669 100644 --- a/modules/apps/transfer/keeper/relay_test.go +++ b/modules/apps/transfer/keeper/relay_test.go @@ -167,6 +167,16 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() { {"tries to unescrow more tokens than allowed", func() { amount = sdk.NewInt(1000000) }, true, false}, + + // - coin being sent to module address on chainA + {"failure: receive on module account", func() { + receiver = suite.chainA.GetSimApp().AccountKeeper.GetModuleAddress(types.ModuleName).String() + }, false, false}, + + // - coin being sent back to original chain (chainB) to module address + {"failure: receive on module account on source chain", func() { + receiver = suite.chainB.GetSimApp().AccountKeeper.GetModuleAddress(types.ModuleName).String() + }, true, false}, } for _, tc := range testCases { diff --git a/modules/apps/transfer/types/expected_keepers.go b/modules/apps/transfer/types/expected_keepers.go index 8ae670d27b2..22ad54b9e62 100644 --- a/modules/apps/transfer/types/expected_keepers.go +++ b/modules/apps/transfer/types/expected_keepers.go @@ -23,6 +23,7 @@ type BankKeeper interface { BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error + BlockedAddr(addr sdk.AccAddress) bool } // ICS4Wrapper defines the expected ICS4Wrapper for middleware From 46e020640e66f9043c14c53a4d215a5b457d6703 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 15 Mar 2022 15:56:42 +0100 Subject: [PATCH 060/140] fixes for the documentation about handling ack for SDK <= 0.45 (#1122) (#1129) * fixes for documentation * review comment Co-authored-by: Carlos Rodriguez (cherry picked from commit 4fe874eb45519b6697948f29c3a48d327b9c3d06) Co-authored-by: Carlos Rodriguez --- docs/apps/interchain-accounts/auth-modules.md | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/docs/apps/interchain-accounts/auth-modules.md b/docs/apps/interchain-accounts/auth-modules.md index b87265d4e28..0e8738f50c0 100644 --- a/docs/apps/interchain-accounts/auth-modules.md +++ b/docs/apps/interchain-accounts/auth-modules.md @@ -220,8 +220,13 @@ If the controller chain is connected to a host chain using the host module on ib 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.Acknowledgement(), txMsgData); err != nil { +if err := proto.Unmarshal(ack.GetResult(), txMsgData); err != nil { return err } ``` @@ -232,6 +237,8 @@ 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 @@ -246,8 +253,8 @@ A router could be used, or more simply a switch statement. ```go func handler(msgData sdk.MsgData) error { -switch msgData.TypeURL { -case banktypes.MsgSend: +switch msgData.MsgType { +case sdk.MsgTypeURL(&banktypes.MsgSend{}): msgResponse := &banktypes.MsgSendResponse{} if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { return err @@ -255,7 +262,7 @@ case banktypes.MsgSend: handleBankSendMsg(msgResponse) -case stakingtypes.MsgDelegate: +case sdk.MsgTypeURL(&stakingtypes.MsgDelegate{}): msgResponse := &stakingtypes.MsgDelegateResponse{} if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { return err @@ -263,7 +270,7 @@ case stakingtypes.MsgDelegate: handleStakingDelegateMsg(msgResponse) -case transfertypes.MsgTransfer: +case sdk.MsgTypeURL(&transfertypes.MsgTransfer{}): msgResponse := &transfertypes.MsgTransferResponse{} if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { return err @@ -281,8 +288,8 @@ The auth module should interpret the txMsgData.Responses as follows: ```go ... -// switch statement from above continued -default: +// switch statement from above +case 0: for _, any := range txMsgData.MsgResponses { if err := handleAny(any); err != nil { return err From 10ebf0d6b0440d19f7b8831c67215ca875469926 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 21 Mar 2022 12:06:32 +0100 Subject: [PATCH 061/140] call packet.GetSequence() rather than passing the func as argument (backport #1130) (#1139) * call packet.GetSequence() rather than passing the func as argument (#1130) * call packet.GetSequence() rather than passing the func as argument * add changelog entry (cherry picked from commit dbd2df26e54f17f94a498d3fe048d68df04735b6) # Conflicts: # CHANGELOG.md * fix conflicts Co-authored-by: Carlos Rodriguez --- CHANGELOG.md | 16 ++++++++++++++++ modules/core/04-channel/keeper/packet.go | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a744185c2ca..0ba29342723 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,22 @@ Ref: https://keepachangelog.com/en/1.0.0/ # Changelog +## [Unreleased] + +### Dependencies + +### API Breaking + +### State Machine Breaking + +### Improvements + +### Features + +### 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 diff --git a/modules/core/04-channel/keeper/packet.go b/modules/core/04-channel/keeper/packet.go index b54926b85e9..51b02f9bece 100644 --- a/modules/core/04-channel/keeper/packet.go +++ b/modules/core/04-channel/keeper/packet.go @@ -360,7 +360,7 @@ func (k Keeper) WriteAcknowledgement( // log that a packet acknowledgement has been written k.Logger(ctx).Info( "acknowledgement written", - "sequence", packet.GetSequence, + "sequence", packet.GetSequence(), "src_port", packet.GetSourcePort(), "src_channel", packet.GetSourceChannel(), "dst_port", packet.GetDestPort(), From d538bc43ee277d0403037dd0a8dd297e5fefee29 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 22 Mar 2022 15:23:53 +0100 Subject: [PATCH 062/140] fixes for go-releaser configuration (#1148) (#1157) * set the pre-release status if the tag contains alpha, beta or rc * add separate filter for final releases (cherry picked from commit 40f9da1efa3f2016c19462d6c2c455812f3241dc) Co-authored-by: Carlos Rodriguez --- .github/workflows/release.yml | 3 ++- .goreleaser.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a1a1da8a340..8397348098c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,7 +3,8 @@ name: Release on: push: tags: - - 'v[0-9]+.[0-9]+.[0-9]+-?[a-z0-9]*' # Push events to matching v*, i.e. v1.0.0, v20.15.10, v3.0.0-alpha1 + - 'v[0-9]+.[0-9]+.[0-9]+' # Push events to matching v*, i.e. v1.0.0, v20.15.10 + - 'v[0-9]+.[0-9]+.[0-9]+-?[a-z0-9]+' # Push events to matching v*-[alpha/beta/rc], i.e. v3.0.0-alpha1, v3.0.0-beta1, v3.0.0-rc0 jobs: goreleaser: diff --git a/.goreleaser.yml b/.goreleaser.yml index a1df128c661..63ecd8822e7 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -24,6 +24,7 @@ checksum: release: mode: keep-existing + prerelease: auto archives: - name_template: "{{ .ProjectName }}_simd_v{{ .Version }}_{{ .Os }}_{{ .Arch }}" @@ -32,4 +33,4 @@ archives: - README.md - RELEASES.md - SECURITY.md - - CHANGELOG.md \ No newline at end of file + - CHANGELOG.md From c0a8e8167a250ca3831768667ba0b1efa990d897 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 28 Mar 2022 17:57:16 +0200 Subject: [PATCH 063/140] add versions for new releases (#1175) (#1183) ## 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. - [x] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [x] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit 46b2720687c3c34282002d8e50bb4f3262c5f035) Co-authored-by: Carlos Rodriguez --- docs/.vuepress/config.js | 22 +++++++++++++++++++++- docs/versions | 5 +++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 8e83c7c7cd3..bdd58cd8d72 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -42,10 +42,30 @@ module.exports = { "label": "v1.2.0", "key": "v1.2.0" }, + { + "label": "v1.3.0", + "key": "v1.3.0" + }, + { + "label": "v1.4.0", + "key": "v1.4.0" + }, { "label": "v2.0.0", "key": "v2.0.0" - } + } , + { + "label": "v2.1.0", + "key": "v2.1.0" + }, + { + "label": "v2.2.0", + "key": "v2.2.0" + }, + { + "label": "v3.0.0", + "key": "v3.0.0" + } ], topbar: { banner: true diff --git a/docs/versions b/docs/versions index 45d6f133c48..e568f9a716f 100644 --- a/docs/versions +++ b/docs/versions @@ -1,4 +1,9 @@ +release/v3.0.x v3.0.0 +release/v2.2.x v2.2.0 +release/v2.1.x v2.1.0 release/v2.0.x v2.0.0 +release/v1.4.x v1.4.0 +release/v1.3.x v1.3.0 release/v1.2.x v1.2.0 release/v1.1.x v1.1.0 main main From ea52a750cdc4efe819f80836b118f8324ef8c538 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 28 Mar 2022 16:05:18 +0000 Subject: [PATCH 064/140] fix typos in the controller params (#1172) (#1182) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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. - [x] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [x] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit 146838200c0096a3b3c56149ca62b7ca2ebfb53e) Co-authored-by: Carlos Rodriguez Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> --- .../apps/27-interchain-accounts/controller/keeper/params.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/apps/27-interchain-accounts/controller/keeper/params.go b/modules/apps/27-interchain-accounts/controller/keeper/params.go index d199b8b554d..dce72c0c491 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/params.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/params.go @@ -6,7 +6,7 @@ import ( "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/controller/types" ) -// IsControllerEnabled retrieves the host enabled boolean from the paramstore. +// IsControllerEnabled retrieves the controller enabled boolean from the paramstore. // True is returned if the controller submodule is enabled. func (k Keeper) IsControllerEnabled(ctx sdk.Context) bool { var res bool @@ -14,12 +14,12 @@ func (k Keeper) IsControllerEnabled(ctx sdk.Context) bool { return res } -// GetParams returns the total set of the host submodule parameters. +// GetParams returns the total set of the controller submodule parameters. func (k Keeper) GetParams(ctx sdk.Context) types.Params { return types.NewParams(k.IsControllerEnabled(ctx)) } -// SetParams sets the total set of the host submodule parameters. +// SetParams sets the total set of the controller submodule parameters. func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { k.paramSpace.SetParamSet(ctx, ¶ms) } From 7662473c7cee2e565a14032dbd166a59f74cb6a6 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 4 Apr 2022 13:27:34 +0200 Subject: [PATCH 065/140] requirements document for ICA (#1173) (#1209) * add requirements document for interchain accounts * fix branch * added number in tittle. * apply suggestions from review Co-authored-by: Aditya * review comment Co-authored-by: Carlos Rodriguez Co-authored-by: Aditya (cherry picked from commit ccc4cb804843f1a80acfb0d4dbf106d1ff2178bb) Co-authored-by: Carlos Rodriguez --- docs/apps/interchain-accounts/requirements.md | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 docs/apps/interchain-accounts/requirements.md diff --git a/docs/apps/interchain-accounts/requirements.md b/docs/apps/interchain-accounts/requirements.md new file mode 100644 index 00000000000..fff919f8088 --- /dev/null +++ b/docs/apps/interchain-accounts/requirements.md @@ -0,0 +1,119 @@ +# 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. | v1 | +| Send over IBC a packet that contains the message to be executed by the interchain account on the host. | v1 | + +# User requirements + +## Use cases + +### Injective <> 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 <> 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 | +| --- | ----------- | ------------ | ------ | +| 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). | `Implemented` | +| 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) | `Implemented` | +| 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) | `Implemented` | +| 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) | `Implemented` | +| 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) | `Implemented` | +| 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) | `Implemented` | +| 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) | `Implemented` | +| 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) | `Implemented` | +| 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) | `Implemented` | + +### 2 - Registration + +| ID | Description | Verification | Status | +| --- | ----------- | ------------ | ------ | +| 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) | `Implemented` | +| 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) | `Implemented` | + +### 3 - Control + +| ID | Description | Verification | Status | +| --- | ----------- | ------------ | ------ | +| 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) | `Implemented` | +| 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). | `Implemented` | + +### 4 - Host execution + +| ID | Description | Verification | Status | +| --- | ----------- | ------------ | ------ | +| 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). | `Implemented` | +| 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) | `Implemented` | +| 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) | `Implemented` | +| 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) | `Implemented` | + +# Non-functional requirements + +## 5 - Security + +| ID | Description | Verification | Status | +| -- | ----------- | ------------ | ------ | +| 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) | `Implemented` | +| 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). | `Implemented` | +| 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. | `Implemented` | +| 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. | `Implemented` | | +| 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) | `Implemented` | + +# External interface requirements + +## 6 - CLI + +| ID | Description | Verification | Status | +| -- | ----------- | ------------ | ------ | +| 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) | `Implemented` | +| 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) | `Implemented` | +| 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) | `Implemented` | + + +## 7 - Application developers + +| ID | Description | Verification | Status | +| -- | ----------- | ------------ | ------ | +| 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. | `Implemented` | +| 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. | `Implemented` | \ No newline at end of file From f49b8bc3c4e9ee2ada83f6c7529fc2e475ccb46e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 6 Apr 2022 09:39:03 +0200 Subject: [PATCH 066/140] imp: improve Logger performance (#1160) (#1218) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: Logger marshal errors * changelog * update (cherry picked from commit ffa98962f9833703b8cd83acf29c3dbfa165701b) Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> --- CHANGELOG.md | 2 ++ modules/core/04-channel/keeper/packet.go | 7 ++++--- modules/core/04-channel/keeper/timeout.go | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ba29342723..d82d8f4f67f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,8 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements +* (modules/core/04-channel) [\#1160](https://github.com/cosmos/ibc-go/pull/1160) Improve `uint64 -> string` performance in `Logger`. + ### Features ### Bug Fixes diff --git a/modules/core/04-channel/keeper/packet.go b/modules/core/04-channel/keeper/packet.go index 51b02f9bece..c3e8e45b89c 100644 --- a/modules/core/04-channel/keeper/packet.go +++ b/modules/core/04-channel/keeper/packet.go @@ -2,6 +2,7 @@ package keeper import ( "bytes" + "strconv" "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -129,7 +130,7 @@ func (k Keeper) SendPacket( k.Logger(ctx).Info( "packet sent", - "sequence", packet.GetSequence(), + "sequence", strconv.FormatUint(packet.GetSequence(), 10), "src_port", packet.GetSourcePort(), "src_channel", packet.GetSourceChannel(), "dst_port", packet.GetDestPort(), @@ -284,7 +285,7 @@ func (k Keeper) RecvPacket( // log that a packet has been received & executed k.Logger(ctx).Info( "packet received", - "sequence", packet.GetSequence(), + "sequence", strconv.FormatUint(packet.GetSequence(), 10), "src_port", packet.GetSourcePort(), "src_channel", packet.GetSourceChannel(), "dst_port", packet.GetDestPort(), @@ -494,7 +495,7 @@ func (k Keeper) AcknowledgePacket( // log that a packet has been acknowledged k.Logger(ctx).Info( "packet acknowledged", - "sequence", packet.GetSequence(), + "sequence", strconv.FormatUint(packet.GetSequence(), 10), "src_port", packet.GetSourcePort(), "src_channel", packet.GetSourceChannel(), "dst_port", packet.GetDestPort(), diff --git a/modules/core/04-channel/keeper/timeout.go b/modules/core/04-channel/keeper/timeout.go index b446aa7ea4c..5a14ef85b6b 100644 --- a/modules/core/04-channel/keeper/timeout.go +++ b/modules/core/04-channel/keeper/timeout.go @@ -2,6 +2,7 @@ package keeper import ( "bytes" + "strconv" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -159,7 +160,7 @@ func (k Keeper) TimeoutExecuted( k.Logger(ctx).Info( "packet timed-out", - "sequence", packet.GetSequence(), + "sequence", strconv.FormatUint(packet.GetSequence(), 10), "src_port", packet.GetSourcePort(), "src_channel", packet.GetSourceChannel(), "dst_port", packet.GetDestPort(), From 73dca924ecdf24c2ac244cd2054975b94cf5d52b Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 14 Apr 2022 12:11:03 +0200 Subject: [PATCH 067/140] docs: replace links in apps with correct ones (#1255) (#1261) ## Description I noticed that 2 links at the bottom of the applications docs page were not correct. 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. - [x] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [x] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit 31d577648124e4b9242b202f6c4521231cbb7b7c) Co-authored-by: Carlos Rodriguez --- docs/ibc/apps.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ibc/apps.md b/docs/ibc/apps.md index bb2716fa0b1..f729d7dfd71 100644 --- a/docs/ibc/apps.md +++ b/docs/ibc/apps.md @@ -464,13 +464,13 @@ 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/types/genesis.go) +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/module.go) +callbacks](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/ibc_module.go) ## Next {hide} From 52f00fb2e7a7c6238b90d1e2de815e4a1edd270d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 14 Apr 2022 12:12:41 +0200 Subject: [PATCH 068/140] update godoc of `RegisterInterchainAccount` (#1256) (#1262) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description The godoc of `RegisterInterchainAccount` was still saying that the port identifier was constructed using as well the connection identifiers. I corrected that. 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. - [x] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [x] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit 4872c4b4a4fba3336664ea5f1206b9857f5ffd4f) Co-authored-by: Carlos Rodriguez Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> --- .../27-interchain-accounts/controller/keeper/account.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/modules/apps/27-interchain-accounts/controller/keeper/account.go b/modules/apps/27-interchain-accounts/controller/keeper/account.go index dd87348876e..03eeef69f1f 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/account.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/account.go @@ -10,11 +10,10 @@ import ( ) // RegisterInterchainAccount is the entry point to registering an interchain account. -// It generates a new port identifier using the owner address, connection identifier, -// and counterparty connection identifier. It will bind to the port identifier and -// call 04-channel 'ChanOpenInit'. 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 MsgChanOpenInit must be used. +// It generates a new port identifier using the owner address. It will bind to the +// port identifier and call 04-channel 'ChanOpenInit'. 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 MsgChanOpenInit must be used. func (k Keeper) RegisterInterchainAccount(ctx sdk.Context, connectionID, owner string) error { portID, err := icatypes.NewControllerPortID(owner) if err != nil { From 70d847248e9e2d446b3a057475dcb7dc3915f041 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 21 Apr 2022 13:10:48 +0200 Subject: [PATCH 069/140] build(deps): bump github.com/cosmos/cosmos-sdk from 0.45.1 to 0.45.3 (backport #1247) (#1271) * build(deps): bump github.com/cosmos/cosmos-sdk from 0.45.1 to 0.45.3 (#1247) Bumps [github.com/cosmos/cosmos-sdk](https://github.com/cosmos/cosmos-sdk) from 0.45.1 to 0.45.3. - [Release notes](https://github.com/cosmos/cosmos-sdk/releases) - [Changelog](https://github.com/cosmos/cosmos-sdk/blob/v0.45.3/CHANGELOG.md) - [Commits](https://github.com/cosmos/cosmos-sdk/compare/v0.45.1...v0.45.3) --- updated-dependencies: - dependency-name: github.com/cosmos/cosmos-sdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> (cherry picked from commit 1d3c1467f6502a9667eff8d48599e7232593a7eb) # Conflicts: # go.mod # go.sum * fix conflicts * delete dead link Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: crodriguezvega --- docs/roadmap/roadmap.md | 1 - go.mod | 49 +++++----- go.sum | 203 ++++++++++++++++++++++++++++++---------- 3 files changed, 178 insertions(+), 75 deletions(-) diff --git a/docs/roadmap/roadmap.md b/docs/roadmap/roadmap.md index e425e4630aa..ceb301740c6 100644 --- a/docs/roadmap/roadmap.md +++ b/docs/roadmap/roadmap.md @@ -59,7 +59,6 @@ During this quarter we will also probably release versions that bump the Cosmos ### H2 January -- [`v2.0.a`](https://github.com/cosmos/ibc-go/milestone/14) - [`v3.0.0-beta1`](https://github.com/cosmos/ibc-go/milestone/12): Beta 1 release of `v3.0.0` including Interchain Accounts, an update of Golang from `v1.15` to `v1.17`, and some core improvements. This is a Go-API breaking release because of [#472](https://github.com/cosmos/ibc-go/issues/472) and [#675](https://github.com/cosmos/ibc-go/pull/675). ### H1 February diff --git a/go.mod b/go.mod index eb9dc1a9548..ab0e814bead 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alp require ( github.com/armon/go-metrics v0.3.10 github.com/confio/ics23/go v0.7.0 - github.com/cosmos/cosmos-sdk v0.45.1 + github.com/cosmos/cosmos-sdk v0.45.3 github.com/gogo/protobuf v1.3.3 github.com/golang/protobuf v1.5.2 github.com/gorilla/mux v1.8.0 @@ -15,14 +15,14 @@ require ( github.com/rakyll/statik v0.1.7 github.com/regen-network/cosmos-proto v0.3.1 github.com/spf13/cast v1.4.1 - github.com/spf13/cobra v1.3.0 + github.com/spf13/cobra v1.4.0 github.com/spf13/viper v1.10.1 - github.com/stretchr/testify v1.7.0 - github.com/tendermint/tendermint v0.34.14 - github.com/tendermint/tm-db v0.6.4 - google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa - google.golang.org/grpc v1.44.0 - google.golang.org/protobuf v1.27.1 + github.com/stretchr/testify v1.7.1 + github.com/tendermint/tendermint v0.34.19 + github.com/tendermint/tm-db v0.6.6 + google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb + google.golang.org/grpc v1.45.0 + google.golang.org/protobuf v1.28.0 gopkg.in/yaml.v2 v2.4.0 ) @@ -31,7 +31,7 @@ require ( github.com/99designs/keyring v1.1.6 // indirect github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect github.com/DataDog/zstd v1.4.5 // indirect - github.com/Workiva/go-datastructures v1.0.52 // indirect + github.com/Workiva/go-datastructures v1.0.53 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect github.com/btcsuite/btcd v0.22.0-beta // indirect @@ -54,15 +54,16 @@ require ( github.com/felixge/httpsnoop v1.0.1 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/gin-gonic/gin v1.7.0 // indirect - github.com/go-kit/kit v0.10.0 // indirect - github.com/go-logfmt/logfmt v0.5.0 // indirect + github.com/go-kit/kit v0.12.0 // indirect + github.com/go-kit/log v0.2.0 // indirect + github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/gateway v1.1.0 // indirect github.com/golang/snappy v0.0.3 // indirect github.com/google/btree v1.0.0 // indirect github.com/google/orderedcode v0.0.1 // indirect github.com/gorilla/handlers v1.5.1 // indirect - github.com/gorilla/websocket v1.4.2 // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/gtank/merlin v0.1.1 // indirect @@ -75,29 +76,27 @@ require ( github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d // indirect - github.com/klauspost/compress v1.11.7 // indirect - github.com/lib/pq v1.10.2 // indirect + github.com/klauspost/compress v1.13.6 // indirect + github.com/lib/pq v1.10.4 // indirect github.com/libp2p/go-buffer-pool v0.0.2 // indirect github.com/magiconair/properties v1.8.5 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 // indirect - github.com/minio/highwayhash v1.0.1 // indirect + github.com/minio/highwayhash v1.0.2 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.4.3 // indirect github.com/mtibben/percent v0.2.1 // indirect - github.com/opencontainers/image-spec v1.0.2 // indirect - github.com/opencontainers/runc v1.0.3 // indirect github.com/pelletier/go-toml v1.9.4 // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.11.0 // indirect + github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.29.0 // indirect - github.com/prometheus/procfs v0.6.0 // indirect + github.com/prometheus/common v0.32.1 // indirect + github.com/prometheus/procfs v0.7.3 // indirect github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect - github.com/rs/cors v1.7.0 // indirect + github.com/rs/cors v1.8.2 // indirect github.com/rs/zerolog v1.23.0 // indirect github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa // indirect github.com/spf13/afero v1.6.0 // indirect @@ -110,10 +109,10 @@ require ( github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 // indirect github.com/tendermint/go-amino v0.16.0 // indirect github.com/zondax/hid v0.9.0 // indirect - go.etcd.io/bbolt v1.3.5 // indirect - golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect - golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f // indirect - golang.org/x/sys v0.0.0-20211210111614-af8b64212486 // indirect + go.etcd.io/bbolt v1.3.6 // indirect + golang.org/x/crypto v0.0.0-20210915214749-c084706c2272 // indirect + golang.org/x/net v0.0.0-20211208012354-db4efeb81f4b // indirect + golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect golang.org/x/text v0.3.7 // indirect gopkg.in/ini.v1 v1.66.2 // indirect diff --git a/go.sum b/go.sum index 5544d71eb5f..63461520875 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= 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= @@ -25,7 +26,6 @@ cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aD 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.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= 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= @@ -53,6 +53,9 @@ github.com/99designs/keyring v1.1.6 h1:kVDC2uCgVwecxCk+9zoCt2uEL6dt+dfVzMvGgnVcI github.com/99designs/keyring v1.1.6/go.mod h1:16e0ds7LGQQcT59QqkTg72Hh5ShM51Byv5PEmW6uoRU= github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= +github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= github.com/Azure/azure-storage-blob-go v0.7.0/go.mod h1:f9YQKtsG1nMisotuTPpO0tjNuEjKRYAcJU8/ydDI++4= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= @@ -68,17 +71,22 @@ github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU= github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= +github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= 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 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= @@ -89,13 +97,16 @@ github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrU github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= 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/Workiva/go-datastructures v1.0.52 h1:PLSK6pwn8mYdaoaCZEMsXBpBotr4HHn9abU0yMQt0NI= github.com/Workiva/go-datastructures v1.0.52/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA= +github.com/Workiva/go-datastructures v1.0.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSaq37xZZh7Yig= +github.com/Workiva/go-datastructures v1.0.53/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A= github.com/Zilliqa/gozilliqa-sdk v1.2.1-0.20201201074141-dd0ecada1be6/go.mod h1:eSYp2T6f0apnuW8TzhV3f6Aff2SE8Dwio++U4ha4yEM= -github.com/adlio/schema v1.1.13 h1:LeNMVg5Z1FX+Qgz8tJUijBLRdcpbFUElz+d1489On98= github.com/adlio/schema v1.1.13/go.mod h1:L5Z7tw+7lRK1Fnpi/LT/ooCP1elkXn0krMWBQHUhEDE= +github.com/adlio/schema v1.3.0 h1:eSVYLxYWbm/6ReZBCkLw4Fz7uqC+ZNoPvA39bOwi52A= +github.com/adlio/schema v1.3.0/go.mod h1:51QzxkpeFs6lRY11kPye26IaFPOV+HqEj01t5aXXKfs= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= 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= @@ -118,7 +129,12 @@ github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6l github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= +github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= +github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= +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= @@ -127,7 +143,6 @@ github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d/go.mod h1:d3C0AkH6BRcvO8T0UEPu53cnw4IbV63x1bEjildYhO0= github.com/btcsuite/btcd v0.0.0-20190315201642-aa6e0f35703c/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= @@ -150,8 +165,10 @@ github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= 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/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/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= @@ -167,6 +184,7 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= 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/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= 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/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod h1:1MxXX1Ux4x6mqPmjkUgTP1CdXIBXKX7T+Jk9Gxrmx+U= @@ -188,8 +206,9 @@ github.com/confio/ics23/go v0.6.6/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4ur github.com/confio/ics23/go v0.7.0 h1:00d2kukk7sPoHWL4zZBZwzxnpA2pec1NPdwbSokJ5w8= github.com/confio/ics23/go v0.7.0/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= -github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 h1:NmTXa/uVnDyp0TY5MKi197+3HWcnYWfnHGyaFthlnGw= github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.2.1 h1:/EeEo2EtN3umhbbgCveyjifoMYg0pS+nMMEemaYw634= +github.com/containerd/continuity v0.2.1/go.mod h1:wCYX+dRqZdImhGucXOqTQn05AhX6EUDaGEMUzTFFpLg= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -203,8 +222,8 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cosmos/btcutil v1.0.4 h1:n7C2ngKXo7UC9gNyMNLbzqz7Asuf+7Qv4gnX/rOdQ44= github.com/cosmos/btcutil v1.0.4/go.mod h1:Ffqc8Hn6TJUdDgHBwIZLtrLQC1KdJ9jGJl/TvgUaxbU= -github.com/cosmos/cosmos-sdk v0.45.1 h1:PY79YxPea5qlRLExRnzg8/rT1Scc8GGgRs22p7DX99Q= -github.com/cosmos/cosmos-sdk v0.45.1/go.mod h1:XXS/asyCqWNWkx2rW6pSuen+EVcpAFxq6khrhnZgHaQ= +github.com/cosmos/cosmos-sdk v0.45.3 h1:PiVSU3IkNEDPhoxOZHk2lPnhwBBJgEYAtAR0jGXRN4g= +github.com/cosmos/cosmos-sdk v0.45.3/go.mod h1:qYm5JEr0ZlbnmoP/Q3b+dYMOliHf4ddHirpILiwZzqg= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= @@ -229,6 +248,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= github.com/dgraph-io/badger/v2 v2.2007.2 h1:EjjK0KqwaFMlPin1ajhP943VPENHJdEz1KLIegjaI3k= @@ -242,6 +262,7 @@ github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WA github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= @@ -282,13 +303,16 @@ github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/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/goblin v0.0.0-20210519012713-85d372ac71e2/go.mod h1:VzmDKDJVZI3aJmnRI9VjAn9nJ8qPPsN1fqzr9dqInIo= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -307,13 +331,17 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 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 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo= github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4= +github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0 h1:7i2K3eKTos3Vc0enKCfnVcgHh2olr/MyfboYq7cAcFw= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= 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 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= @@ -325,9 +353,10 @@ github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7a github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 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-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= @@ -340,6 +369,10 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x github.com/gogo/gateway v1.1.0 h1:u0SuhL9+Il+UbjM9VIE3ntfRujKbvVpFvNB4HbjeVQ0= github.com/gogo/gateway v1.1.0/go.mod h1:S7rR8FRQyG3QFESeSv4l2WnsyzlCLG0CzBbUUo/mbic= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188/go.mod h1:vXjM/+wXQnTPR4KqTKDgJukSZ6amVRtWMPEjE6sQoK8= +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/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -439,10 +472,12 @@ github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -465,7 +500,8 @@ github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uM github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= -github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= +github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= +github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= @@ -474,6 +510,7 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= 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= @@ -512,6 +549,7 @@ github.com/hdevalence/ed25519consensus v0.0.0-20210204194344-59a8610d2b87/go.mod github.com/holiman/uint256 v1.1.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= @@ -523,6 +561,7 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -530,6 +569,8 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS github.com/jhump/protoreflect v1.9.0 h1:npqHz788dryJiR/l6K/RUQAyh2SwV91+d1dnh4RjO9w= github.com/jhump/protoreflect v1.9.0/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +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= @@ -549,6 +590,7 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 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.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d h1:Z+RDyXzjKE0i2sTjZ/b1uxiGtPhFy34Ou/Tk0qwN0kM= github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc= @@ -556,8 +598,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.7 h1:0hzRabrMN4tSTvMfnL3SCv1ZGeAP23ynzodBgaHeMeg= github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 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= @@ -573,8 +617,8 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+ github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= +github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= @@ -607,15 +651,18 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/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.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 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/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 h1:hLDRPB66XQT/8+wG9WsDpiCvZf1yKO7sz7scAjSlBa0= github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM= -github.com/minio/highwayhash v1.0.1 h1:dZ6IIu8Z14VlC0VpfKofAhCy74wu/Qb5gcn52yWoz/0= github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= +github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= +github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -627,7 +674,7 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu 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.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= @@ -638,6 +685,7 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN 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/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= 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= @@ -649,10 +697,16 @@ github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= 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/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q= +github.com/nats-io/jwt/v2 v2.0.3/go.mod h1:VRP+deawSXyhNjXmxPCHskrR6Mq50BqpEI5SEcNiGlY= github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats-server/v2 v2.5.0/go.mod h1:Kj86UtrXAL6LwYRA6H4RqzkHhK0Vcv2ZnKD5WbQ1t3g= github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nats.go v1.12.1/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= 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/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s= +github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/neilotoole/errgroup v0.1.5/go.mod h1:Q2nLGf+594h0CLBs/Mbg6qOr7GtqDK7C2S41udRnToE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= @@ -681,8 +735,9 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak= github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= -github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +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.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= @@ -696,10 +751,12 @@ github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go 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/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 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/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= 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/otiai10/copy v1.6.0 h1:IinKAryFFuPONZ7cm6T6E2QX/vcJwSnlaA5lfoaXIiQ= @@ -715,15 +772,17 @@ github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144T github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= +github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= 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/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= 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= @@ -742,8 +801,9 @@ github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeD 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.8.0/go.mod h1:O9VU6huf47PktckDQfMTX0Y8tY0/7TSWwj+ITvv0TnM= -github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= 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= @@ -761,8 +821,9 @@ github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB8 github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= 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.29.0 h1:3jqPBvKT4OHAbje2Ql7KeaaSicDBCxMYwEJU1zRJceE= -github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= 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.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -771,8 +832,9 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0/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 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= @@ -789,8 +851,9 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So 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/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= +github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.23.0 h1:UskrK+saS9P9Y789yNNulYKdARjPZuS35B8gJF2x60g= @@ -799,7 +862,7 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR 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/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= +github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa h1:0U2s5loxrTy6/VgfVoLuVLFJcURKLH49ie0zSch7gh4= github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= @@ -828,15 +891,14 @@ github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY52 github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= -github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0= -github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= +github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= +github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -845,10 +907,9 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= -github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk= github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= @@ -856,7 +917,9 @@ github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8 github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU= 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/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e/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.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= @@ -866,8 +929,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV 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 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -881,19 +945,25 @@ github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 h1:hqAk8riJvK4RM github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15/go.mod h1:z4YtwM70uOnk8h0pjJYlj3zdYwi9l03By6iAIF5j/Pk= 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.34.14 h1:GCXmlS8Bqd2Ix3TQCpwYLUNHe+Y+QyJsm5YE+S/FkPo= github.com/tendermint/tendermint v0.34.14/go.mod h1:FrwVm3TvsVicI9Z7FlucHV6Znfd5KBc/Lpp69cCwtk0= -github.com/tendermint/tm-db v0.6.4 h1:3N2jlnYQkXNQclQwd/eKV/NzlqPlfK21cpRRIx80XXQ= +github.com/tendermint/tendermint v0.34.19 h1:y0P1qI5wSa9IRuhKnTDA6IUcOrLi1hXJuALR+R7HFEk= +github.com/tendermint/tendermint v0.34.19/go.mod h1:R5+wgIwSxMdKQcmOaeudL0Cjkr3HDkhpcdum6VeU3R4= github.com/tendermint/tm-db v0.6.4/go.mod h1:dptYhIpJ2M5kUuenLr+Yyf3zQOv1SgBZcl8/BmWlMBw= +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/tidwall/gjson v1.6.7/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/sjson v1.1.4/go.mod h1:wXpKXu8CtDjKAZ+3DrKY5ROCorDFahq8l0tey/Lx1fg= +github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -918,8 +988,9 @@ github.com/zondax/hid v0.9.0 h1:eiT3P6vNxAEVxXMw66eZUAAnU2zD33JBkfG/EnfAKl8= github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= @@ -927,6 +998,7 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3 go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= +go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= 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= @@ -941,13 +1013,17 @@ 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/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 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.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= 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.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 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= @@ -963,14 +1039,22 @@ golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= +golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210915214749-c084706c2272 h1:3erb+vDS8lU1sxfDHF4/hhWyaXnhIaO+7RgL4fDZORA= +golang.org/x/crypto v0.0.0-20210915214749-c084706c2272/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +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-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= @@ -982,6 +1066,7 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 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/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/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1026,6 +1111,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn 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-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 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= @@ -1064,9 +1150,13 @@ golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f h1:w6wWR0H+nyVpbSAQbzVEIACVyr/h8l/BEkY6Sokc7Eg= golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211208012354-db4efeb81f4b h1:MWaHNqZy3KTpuTMAGvv+Kw+ylsEpmyJZizz1dqxnu28= +golang.org/x/net v0.0.0-20211208012354-db4efeb81f4b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 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= @@ -1078,7 +1168,6 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ 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-20210402161424-2e8d93401602/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= @@ -1130,6 +1219,7 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191115151921-52ab43148777/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-20191210023423-ac6580df4449/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= @@ -1156,6 +1246,7 @@ golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1185,12 +1276,14 @@ golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210903071746-97244b99971b/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-20210917161153-d61c044b1678/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-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486 h1:5hpz5aRr+W1erYCL5JRhSUBJRph7l9XkNveoExlrKYk= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1208,9 +1301,13 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb 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-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +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= @@ -1260,6 +1357,7 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc 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-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/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= @@ -1277,6 +1375,10 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +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/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= 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= @@ -1299,7 +1401,6 @@ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34q 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.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= 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= @@ -1310,7 +1411,7 @@ google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqiv google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= 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= @@ -1380,15 +1481,15 @@ google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEc 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-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/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 h1:I0YcKz0I7OAhddo7ya8kMnvprhcWM045PmkBdMO9zN0= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb h1:ZrsicilzPCS/Xr8qtBZZLpy4P9TYXAfl49ctG1/5tgw= +google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= 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.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -1423,8 +1524,9 @@ google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD 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 h1:weqSxi/TMs1SqFRMHCtBgXRs8k3X39QIDEZ0pRcttUg= -google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= 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= @@ -1439,12 +1541,14 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12/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 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= @@ -1452,7 +1556,6 @@ 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/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= @@ -1487,7 +1590,9 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k= nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= 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.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= From 06455533a1f69e83cd24cd77621322fe7c4dc6b1 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 25 Apr 2022 11:28:30 +0200 Subject: [PATCH 070/140] add missing changelog entry for bump of sdk 0.45.3 (#1272) (#1277) ## 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. - [x] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [x] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [x] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit 23fccc53f16ada57191922b55b22c71e7bc40acf) Co-authored-by: Carlos Rodriguez --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d82d8f4f67f..ee25c9705d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,8 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Dependencies +* [\#1247](https://github.com/cosmos/ibc-go/pull/1247) Bump SDK version to v0.45.3 and Tendermint to version 0.34.19 + ### API Breaking ### State Machine Breaking From 58832279220a053200e3729ffa29adcd704d5f8d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 27 Apr 2022 13:30:56 +0200 Subject: [PATCH 071/140] fixing dead links from sdk default branch update (#1301) (#1303) (cherry picked from commit bace9885f56e30daf00266f954764bcfcd667fbf) Co-authored-by: Damian Nolan --- docs/apps/interchain-accounts/transactions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/apps/interchain-accounts/transactions.md b/docs/apps/interchain-accounts/transactions.md index a78a917387f..e17571a6e00 100644 --- a/docs/apps/interchain-accounts/transactions.md +++ b/docs/apps/interchain-accounts/transactions.md @@ -16,6 +16,6 @@ Transactions are executed via the ICS27 [`SendTx` API](./auth-modules.md#trysend ## 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/master/core/store.html#cachemultistore) architecture provided by the [`Context`](https://docs.cosmos.network/master/core/context.html) type. +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/core/store.html#cachemultistore) architecture provided by the [`Context`](https://docs.cosmos.network/main/core/context.html) type. This provides atomic execution of transactions when using Interchain Accounts, where state changes are only committed if all `Msg`s succeed. From 33b063ec49bc85a404a096c87caf7a1a1ebe6f46 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 17 May 2022 17:06:47 +0200 Subject: [PATCH 072/140] override default docsBranch (#1355) (#1389) ## Description closes: #1354 --- 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. - [x] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [x] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [x] Re-reviewed `Files changed` in the Github PR explorer - [x] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit 2709c241b0412ee2e6887bc818f24418cdef469f) Co-authored-by: Carlos Rodriguez --- docs/.vuepress/config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index bdd58cd8d72..43bd93590cd 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -20,6 +20,7 @@ module.exports = { themeConfig: { repo: "cosmos/ibc-go", docsRepo: "cosmos/ibc-go", + docsBranch: "main", docsDir: "docs", editLinks: true, label: "ibc", From 75ee5486a2407b7751f204ca6d4a8c0f69318ea8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 18 May 2022 09:26:17 +0200 Subject: [PATCH 073/140] build(deps): bump github.com/cosmos/cosmos-sdk from 0.45.3 to 0.45.4 (#1300) (#1364) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * build(deps): bump github.com/cosmos/cosmos-sdk from 0.45.3 to 0.45.4 Bumps [github.com/cosmos/cosmos-sdk](https://github.com/cosmos/cosmos-sdk) from 0.45.3 to 0.45.4. - [Release notes](https://github.com/cosmos/cosmos-sdk/releases) - [Changelog](https://github.com/cosmos/cosmos-sdk/blob/v0.45.4/CHANGELOG.md) - [Commits](https://github.com/cosmos/cosmos-sdk/compare/v0.45.3...v0.45.4) --- updated-dependencies: - dependency-name: github.com/cosmos/cosmos-sdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * update changelog Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> Co-authored-by: Carlos Rodriguez (cherry picked from commit bd086506f9256c4d5776bc38d2e930b523eb5125) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- CHANGELOG.md | 2 +- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee25c9705d7..7af77f82ebd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,7 +38,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Dependencies -* [\#1247](https://github.com/cosmos/ibc-go/pull/1247) Bump SDK version to v0.45.3 and Tendermint to version 0.34.19 +* [\#1300](https://github.com/cosmos/ibc-go/pull/1300) Bump SDK version to v0.45.4 ### API Breaking diff --git a/go.mod b/go.mod index ab0e814bead..a95188cd377 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alp require ( github.com/armon/go-metrics v0.3.10 github.com/confio/ics23/go v0.7.0 - github.com/cosmos/cosmos-sdk v0.45.3 + github.com/cosmos/cosmos-sdk v0.45.4 github.com/gogo/protobuf v1.3.3 github.com/golang/protobuf v1.5.2 github.com/gorilla/mux v1.8.0 diff --git a/go.sum b/go.sum index 63461520875..5cb85ae6a99 100644 --- a/go.sum +++ b/go.sum @@ -222,8 +222,8 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cosmos/btcutil v1.0.4 h1:n7C2ngKXo7UC9gNyMNLbzqz7Asuf+7Qv4gnX/rOdQ44= github.com/cosmos/btcutil v1.0.4/go.mod h1:Ffqc8Hn6TJUdDgHBwIZLtrLQC1KdJ9jGJl/TvgUaxbU= -github.com/cosmos/cosmos-sdk v0.45.3 h1:PiVSU3IkNEDPhoxOZHk2lPnhwBBJgEYAtAR0jGXRN4g= -github.com/cosmos/cosmos-sdk v0.45.3/go.mod h1:qYm5JEr0ZlbnmoP/Q3b+dYMOliHf4ddHirpILiwZzqg= +github.com/cosmos/cosmos-sdk v0.45.4 h1:eStDAhJdMY8n5arbBRe+OwpNeBSunxSBHp1g55ulfdA= +github.com/cosmos/cosmos-sdk v0.45.4/go.mod h1:WOqtDxN3eCCmnYLVla10xG7lEXkFjpTaqm2a2WasgCc= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= From f58b8c9566904bbc3457f41a95032af54873ee10 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 18 May 2022 20:52:31 +0200 Subject: [PATCH 074/140] add empty keepers checking in ibc NewKeeper (backport #1284) (#1381) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add empty keepers checking in ibc NewKeeper (#1284) * add empty keepers checking in ibc NewKeeper * check for empty exported keepers instead of empty sdk-defined keeper structs * Update modules/core/keeper/keeper.go Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> * remove func checkEmptyKeepers(), check empty keepers directly within func NewKeeper() * modules/core/keeper KeeperTestSuite -> MsgServerTestSuite; creat new modules/core/keeper KeeperTestSuite for testing ibckeeper.NewKeeper() * update CHANGELOG.md * DummyStakingKeeper -> MockStakingKeeper * refactor modules/core/keeper test * Update modules/core/keeper/keeper_test.go Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> * Update modules/core/keeper/keeper.go Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> (cherry picked from commit f2577f97c5341c09232a0c56d8a1a62ad6ea0b8a) # Conflicts: # CHANGELOG.md * fix conflict * fix conflict Co-authored-by: khanh <50263489+catShaark@users.noreply.github.com> Co-authored-by: crodriguezvega --- CHANGELOG.md | 1 + modules/core/keeper/keeper.go | 16 +++ modules/core/keeper/keeper_test.go | 138 +++++++++++++++++++++++++ modules/core/keeper/msg_server_test.go | 32 ------ 4 files changed, 155 insertions(+), 32 deletions(-) create mode 100644 modules/core/keeper/keeper_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 7af77f82ebd..a6880b9f6e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements * (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. ### Features diff --git a/modules/core/keeper/keeper.go b/modules/core/keeper/keeper.go index 69044e9e4ea..3a6cc5cb23b 100644 --- a/modules/core/keeper/keeper.go +++ b/modules/core/keeper/keeper.go @@ -1,6 +1,9 @@ package keeper import ( + "fmt" + "reflect" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" @@ -46,6 +49,19 @@ func NewKeeper( paramSpace = paramSpace.WithKeyTable(keyTable) } + // panic if any of the keepers passed in is empty + if reflect.ValueOf(stakingKeeper).IsZero() { + panic(fmt.Errorf("cannot initialize IBC keeper: empty staking keeper")) + } + + if reflect.ValueOf(upgradeKeeper).IsZero() { + panic(fmt.Errorf("cannot initialize IBC keeper: empty upgrade keeper")) + } + + if reflect.DeepEqual(capabilitykeeper.ScopedKeeper{}, scopedKeeper) { + panic(fmt.Errorf("cannot initialize IBC keeper: empty scoped keeper")) + } + clientKeeper := clientkeeper.NewKeeper(cdc, key, paramSpace, stakingKeeper, upgradeKeeper) connectionKeeper := connectionkeeper.NewKeeper(cdc, key, paramSpace, clientKeeper) portKeeper := portkeeper.NewKeeper(scopedKeeper) diff --git a/modules/core/keeper/keeper_test.go b/modules/core/keeper/keeper_test.go new file mode 100644 index 00000000000..d40d1ab34c5 --- /dev/null +++ b/modules/core/keeper/keeper_test.go @@ -0,0 +1,138 @@ +package keeper_test + +import ( + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" + "github.com/stretchr/testify/suite" + + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + upgradekeeper "github.com/cosmos/cosmos-sdk/x/upgrade/keeper" + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + ibchost "github.com/cosmos/ibc-go/v3/modules/core/24-host" + ibckeeper "github.com/cosmos/ibc-go/v3/modules/core/keeper" + ibctesting "github.com/cosmos/ibc-go/v3/testing" +) + +type KeeperTestSuite struct { + suite.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) { + suite.Run(t, new(KeeperTestSuite)) +} + +// MockStakingKeeper implements clienttypes.StakingKeeper used in ibckeeper.NewKeeper +type MockStakingKeeper struct { + mockField string +} + +func (d MockStakingKeeper) GetHistoricalInfo(ctx sdk.Context, height int64) (stakingtypes.HistoricalInfo, bool) { + return stakingtypes.HistoricalInfo{}, true +} + +func (d MockStakingKeeper) UnbondingTime(ctx sdk.Context) time.Duration { + return 0 +} + +// 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 ( + stakingKeeper clienttypes.StakingKeeper + upgradeKeeper clienttypes.UpgradeKeeper + scopedKeeper capabilitykeeper.ScopedKeeper + newIBCKeeper = func() { + ibckeeper.NewKeeper( + suite.chainA.GetSimApp().AppCodec(), + suite.chainA.GetSimApp().GetKey(ibchost.StoreKey), + suite.chainA.GetSimApp().GetSubspace(ibchost.ModuleName), + stakingKeeper, + upgradeKeeper, + scopedKeeper, + ) + } + ) + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + {"failure: empty staking keeper", func() { + emptyStakingKeeper := stakingkeeper.Keeper{} + + stakingKeeper = emptyStakingKeeper + + }, false}, + {"failure: empty mock staking keeper", func() { + // use a different implementation of clienttypes.StakingKeeper + emptyMockStakingKeeper := MockStakingKeeper{} + + stakingKeeper = emptyMockStakingKeeper + + }, false}, + {"failure: empty upgrade keeper", func() { + emptyUpgradeKeeper := upgradekeeper.Keeper{} + + upgradeKeeper = emptyUpgradeKeeper + + }, false}, + {"failure: empty scoped keeper", func() { + emptyScopedKeeper := capabilitykeeper.ScopedKeeper{} + + scopedKeeper = emptyScopedKeeper + }, false}, + {"success: replace stakingKeeper with non-empty MockStakingKeeper", func() { + // use a different implementation of clienttypes.StakingKeeper + mockStakingKeeper := MockStakingKeeper{"not empty"} + + stakingKeeper = mockStakingKeeper + }, true}, + } + + for _, tc := range testCases { + tc := tc + suite.SetupTest() + + suite.Run(tc.name, func() { + + stakingKeeper = suite.chainA.GetSimApp().StakingKeeper + upgradeKeeper = suite.chainA.GetSimApp().UpgradeKeeper + scopedKeeper = suite.chainA.GetSimApp().ScopedIBCKeeper + + tc.malleate() + + if tc.expPass { + suite.Require().NotPanics( + newIBCKeeper, + ) + } else { + suite.Require().Panics( + newIBCKeeper, + ) + } + + }) + } +} diff --git a/modules/core/keeper/msg_server_test.go b/modules/core/keeper/msg_server_test.go index 1e1933ee724..7e01241be81 100644 --- a/modules/core/keeper/msg_server_test.go +++ b/modules/core/keeper/msg_server_test.go @@ -1,11 +1,8 @@ package keeper_test import ( - "testing" - sdk "github.com/cosmos/cosmos-sdk/types" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" - "github.com/stretchr/testify/suite" clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" @@ -18,40 +15,11 @@ import ( ibcmock "github.com/cosmos/ibc-go/v3/testing/mock" ) -const height = 10 - var ( timeoutHeight = clienttypes.NewHeight(0, 10000) maxSequence = uint64(10) ) -type KeeperTestSuite struct { - suite.Suite - - coordinator *ibctesting.Coordinator - - chainA *ibctesting.TestChain - chainB *ibctesting.TestChain -} - -// SetupTest creates a coordinator with 2 test chains. -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 TestIBCTestSuite(t *testing.T) { - suite.Run(t, new(KeeperTestSuite)) -} - // 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 From 8111f7ab308e938ef4875f8183d8aafdd632b027 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 18 May 2022 21:22:08 +0200 Subject: [PATCH 075/140] fix: prefix ResponseResultType enum for proto linting (#1143) (#1392) (cherry picked from commit 5cf580cbd9e87a6385928b7440b26f7e92b37c43) Co-authored-by: Damian Nolan Co-authored-by: Carlos Rodriguez --- docs/ibc/proto-docs.md | 6 +- modules/core/04-channel/types/tx.pb.go | 174 ++++++++++++------------- proto/ibc/core/channel/v1/tx.proto | 6 +- 3 files changed, 93 insertions(+), 93 deletions(-) diff --git a/docs/ibc/proto-docs.md b/docs/ibc/proto-docs.md index 80b2df196d3..abcc6597743 100644 --- a/docs/ibc/proto-docs.md +++ b/docs/ibc/proto-docs.md @@ -2048,9 +2048,9 @@ ResponseResultType defines the possible outcomes of the execution of a message | Name | Number | Description | | ---- | ------ | ----------- | -| RESPONSE_RESULT_UNSPECIFIED | 0 | Default zero value enumeration | -| RESPONSE_RESULT_NOOP | 1 | The message did not call the IBC application callbacks (because, for example, the packet had already been relayed) | -| RESPONSE_RESULT_SUCCESS | 2 | The message was executed successfully | +| RESPONSE_RESULT_TYPE_UNSPECIFIED | 0 | Default zero value enumeration | +| RESPONSE_RESULT_TYPE_NOOP | 1 | The message did not call the IBC application callbacks (because, for example, the packet had already been relayed) | +| RESPONSE_RESULT_TYPE_SUCCESS | 2 | The message was executed successfully | diff --git a/modules/core/04-channel/types/tx.pb.go b/modules/core/04-channel/types/tx.pb.go index e497cf802b1..8f9ebe6ec63 100644 --- a/modules/core/04-channel/types/tx.pb.go +++ b/modules/core/04-channel/types/tx.pb.go @@ -42,15 +42,15 @@ const ( ) var ResponseResultType_name = map[int32]string{ - 0: "RESPONSE_RESULT_UNSPECIFIED", - 1: "RESPONSE_RESULT_NOOP", - 2: "RESPONSE_RESULT_SUCCESS", + 0: "RESPONSE_RESULT_TYPE_UNSPECIFIED", + 1: "RESPONSE_RESULT_TYPE_NOOP", + 2: "RESPONSE_RESULT_TYPE_SUCCESS", } var ResponseResultType_value = map[string]int32{ - "RESPONSE_RESULT_UNSPECIFIED": 0, - "RESPONSE_RESULT_NOOP": 1, - "RESPONSE_RESULT_SUCCESS": 2, + "RESPONSE_RESULT_TYPE_UNSPECIFIED": 0, + "RESPONSE_RESULT_TYPE_NOOP": 1, + "RESPONSE_RESULT_TYPE_SUCCESS": 2, } func (x ResponseResultType) String() string { @@ -902,87 +902,87 @@ func init() { func init() { proto.RegisterFile("ibc/core/channel/v1/tx.proto", fileDescriptor_bc4637e0ac3fc7b7) } var fileDescriptor_bc4637e0ac3fc7b7 = []byte{ - // 1267 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0x4b, 0x6f, 0xdb, 0x46, - 0x10, 0xd6, 0xcb, 0xb2, 0x3d, 0x72, 0x6d, 0x99, 0xf2, 0x43, 0xa1, 0x62, 0x51, 0xe5, 0x21, 0x11, - 0x5c, 0x44, 0xf2, 0x23, 0x40, 0x11, 0xa3, 0x40, 0x61, 0xa9, 0x0a, 0x6a, 0xb4, 0x7e, 0x80, 0xb2, - 0x7b, 0x70, 0x8b, 0x0a, 0x12, 0xb5, 0x91, 0x09, 0x49, 0x5c, 0x95, 0xa4, 0x94, 0xe8, 0x1f, 0x04, - 0x3e, 0xe5, 0x6c, 0xc0, 0x40, 0x8a, 0x1e, 0x7b, 0x48, 0x7f, 0x46, 0x8e, 0x39, 0xb5, 0x45, 0x0f, - 0x42, 0x61, 0x5f, 0x7a, 0xd6, 0x2f, 0x28, 0xb8, 0x5c, 0x52, 0x94, 0x44, 0xc2, 0x74, 0x62, 0x3b, - 0xb9, 0xed, 0xce, 0x7c, 0x3b, 0x3b, 0xfb, 0x7d, 0xc3, 0x7d, 0x10, 0xee, 0x4b, 0x15, 0x31, 0x2b, - 0x62, 0x05, 0x65, 0xc5, 0x93, 0xb2, 0x2c, 0xa3, 0x46, 0xb6, 0xb3, 0x9e, 0xd5, 0x5e, 0x64, 0x5a, - 0x0a, 0xd6, 0x30, 0x13, 0x93, 0x2a, 0x62, 0x46, 0xf7, 0x66, 0xa8, 0x37, 0xd3, 0x59, 0x67, 0x17, - 0x6a, 0xb8, 0x86, 0x89, 0x3f, 0xab, 0xb7, 0x0c, 0x28, 0xcb, 0x0d, 0x02, 0x35, 0x24, 0x24, 0x6b, - 0x7a, 0x1c, 0xa3, 0x45, 0x01, 0x9f, 0x3b, 0xcd, 0x64, 0x86, 0x25, 0x10, 0xfe, 0x57, 0x3f, 0x30, - 0xbb, 0x6a, 0x2d, 0x6f, 0x18, 0xf7, 0x5b, 0x48, 0xde, 0x91, 0x25, 0x8d, 0xf9, 0x02, 0x26, 0x5b, - 0x58, 0xd1, 0x4a, 0x52, 0x35, 0xee, 0x4f, 0xf9, 0xd3, 0xd3, 0x39, 0xa6, 0xdf, 0xe3, 0x66, 0xbb, - 0xe5, 0x66, 0x63, 0x8b, 0xa7, 0x0e, 0x5e, 0x08, 0xeb, 0xad, 0x9d, 0x2a, 0xf3, 0x15, 0x4c, 0xd2, - 0xa0, 0xf1, 0x40, 0xca, 0x9f, 0x8e, 0x6c, 0xdc, 0xcf, 0x38, 0x2c, 0x22, 0x43, 0xe7, 0xc8, 0x85, - 0xde, 0xf6, 0x38, 0x9f, 0x60, 0x0e, 0x61, 0x96, 0x20, 0xac, 0x4a, 0x35, 0x19, 0x29, 0xf1, 0xa0, - 0x3e, 0x93, 0x40, 0x7b, 0x5b, 0x53, 0x2f, 0x5f, 0x73, 0xbe, 0xff, 0x5e, 0x73, 0x3e, 0x5e, 0x00, - 0x76, 0x3c, 0x45, 0x01, 0xa9, 0x2d, 0x2c, 0xab, 0x88, 0x79, 0x0c, 0x40, 0x43, 0x0d, 0xb2, 0x5d, - 0xec, 0xf7, 0xb8, 0x79, 0x23, 0xdb, 0x81, 0x8f, 0x17, 0xa6, 0x69, 0x67, 0xa7, 0xca, 0xff, 0x19, - 0x84, 0xf9, 0xe1, 0xa0, 0x87, 0x4a, 0xf7, 0x7a, 0xcb, 0xde, 0x83, 0x58, 0x4b, 0x41, 0x1d, 0x09, - 0xb7, 0xd5, 0x92, 0x2d, 0x83, 0x00, 0x19, 0x98, 0xec, 0xf7, 0x38, 0x96, 0x0e, 0x1c, 0x07, 0xf1, - 0xc2, 0xbc, 0x69, 0xcd, 0x9b, 0x29, 0xd9, 0x69, 0x0c, 0x5e, 0x9f, 0x46, 0x01, 0x16, 0x44, 0xdc, - 0x96, 0x35, 0xa4, 0xb4, 0xca, 0x8a, 0xd6, 0x2d, 0x75, 0x90, 0xa2, 0x4a, 0x58, 0x8e, 0x87, 0x48, - 0x3a, 0x5c, 0xbf, 0xc7, 0x25, 0x28, 0x21, 0x0e, 0x28, 0x5e, 0x88, 0xd9, 0xcd, 0x3f, 0x18, 0x56, - 0x9d, 0xda, 0x96, 0x82, 0xf1, 0xb3, 0x92, 0x24, 0x4b, 0x5a, 0x7c, 0x22, 0xe5, 0x4f, 0xcf, 0xd8, - 0xa9, 0x1d, 0xf8, 0x78, 0x61, 0x9a, 0x74, 0x48, 0xed, 0x1c, 0xc3, 0x8c, 0xe1, 0x39, 0x41, 0x52, - 0xed, 0x44, 0x8b, 0x87, 0xc9, 0x62, 0x58, 0xdb, 0x62, 0x8c, 0x1a, 0xed, 0xac, 0x67, 0xbe, 0x25, - 0x88, 0x5c, 0x42, 0x5f, 0x4a, 0xbf, 0xc7, 0xc5, 0xec, 0x71, 0x8d, 0xd1, 0xbc, 0x10, 0x21, 0x5d, - 0x03, 0x69, 0x2b, 0x96, 0x49, 0x97, 0x62, 0x49, 0xc0, 0xbd, 0x31, 0x5d, 0xcd, 0x5a, 0xe1, 0xff, - 0x1a, 0x53, 0x7d, 0x5b, 0xac, 0x5f, 0x4f, 0xf5, 0xe1, 0x72, 0x0b, 0x78, 0x2b, 0x37, 0xe6, 0x18, - 0x96, 0x87, 0x78, 0xb7, 0x85, 0x20, 0x55, 0x9f, 0xe3, 0xfb, 0x3d, 0x2e, 0xe9, 0x20, 0x90, 0x3d, - 0xde, 0xa2, 0xdd, 0x33, 0xa8, 0x9b, 0xdb, 0x50, 0x7e, 0x1d, 0x0c, 0x41, 0x4b, 0x9a, 0xd2, 0xa5, - 0xc2, 0x2f, 0xf4, 0x7b, 0x5c, 0xd4, 0x2e, 0x90, 0xa6, 0x74, 0x79, 0x61, 0x8a, 0xb4, 0xf5, 0x6f, - 0xe7, 0x13, 0x93, 0x7d, 0x5b, 0xac, 0x5b, 0xb2, 0xff, 0x1e, 0x80, 0xc5, 0x61, 0x6f, 0x1e, 0xcb, - 0xcf, 0x24, 0xa5, 0x79, 0x17, 0xd2, 0x5b, 0x54, 0x96, 0xc5, 0x3a, 0x11, 0xdb, 0x81, 0xca, 0xb2, - 0x58, 0x37, 0xa9, 0xd4, 0x0b, 0x72, 0x94, 0xca, 0xd0, 0xad, 0x50, 0x39, 0xe1, 0x42, 0x25, 0x07, - 0x2b, 0x8e, 0x64, 0x59, 0x74, 0x9e, 0xf9, 0x21, 0x36, 0x40, 0xe4, 0x1b, 0x58, 0x45, 0xd7, 0x3f, - 0x34, 0xde, 0x8f, 0xcc, 0xab, 0x0f, 0x8b, 0x15, 0x48, 0x38, 0xe4, 0x66, 0xe5, 0xfe, 0x26, 0x00, - 0x4b, 0x23, 0xfe, 0x3b, 0xac, 0x85, 0xe1, 0x0d, 0x35, 0xf8, 0x9e, 0x1b, 0xea, 0xdd, 0x96, 0x43, - 0x0a, 0x92, 0xce, 0x84, 0x59, 0x9c, 0xbe, 0x0a, 0xc0, 0x67, 0xbb, 0x6a, 0x4d, 0x40, 0x62, 0xe7, - 0xa0, 0x2c, 0xd6, 0x91, 0xc6, 0x3c, 0x81, 0x70, 0x8b, 0xb4, 0x08, 0x93, 0x91, 0x8d, 0x84, 0xe3, - 0x49, 0x66, 0x80, 0xe9, 0x41, 0x46, 0x07, 0x30, 0x4f, 0x21, 0x6a, 0xa4, 0x2b, 0xe2, 0x66, 0x53, - 0xd2, 0x9a, 0x48, 0xd6, 0x08, 0xbd, 0x33, 0xb9, 0x44, 0xbf, 0xc7, 0x2d, 0xdb, 0x17, 0x34, 0x40, - 0xf0, 0xc2, 0x1c, 0x31, 0xe5, 0x2d, 0xcb, 0x18, 0x69, 0xc1, 0x5b, 0x21, 0x2d, 0xe4, 0x42, 0xda, - 0xcf, 0x64, 0xc3, 0x19, 0x30, 0x62, 0xdd, 0x56, 0xbe, 0x86, 0xb0, 0x82, 0xd4, 0x76, 0xc3, 0x60, - 0x66, 0x76, 0xe3, 0xa1, 0x23, 0x33, 0x26, 0x5c, 0x20, 0xd0, 0xc3, 0x6e, 0x0b, 0x09, 0x74, 0xd8, - 0x56, 0x48, 0x9f, 0x83, 0xff, 0x27, 0x00, 0xb0, 0xab, 0xd6, 0x0e, 0xa5, 0x26, 0xc2, 0xed, 0x9b, - 0xe1, 0xbb, 0x2d, 0x2b, 0x48, 0x44, 0x52, 0x07, 0x55, 0xdd, 0xf8, 0x1e, 0x20, 0x4c, 0xbe, 0x8f, - 0x2c, 0xcb, 0xad, 0xf2, 0xfd, 0x1d, 0x30, 0x32, 0x7a, 0xa1, 0x95, 0x54, 0xf4, 0x4b, 0x1b, 0xc9, - 0x22, 0x2a, 0x29, 0x48, 0xec, 0x10, 0xee, 0x43, 0xb9, 0x95, 0x7e, 0x8f, 0xbb, 0x67, 0x44, 0x18, - 0xc7, 0xf0, 0x42, 0x54, 0x37, 0x16, 0xa9, 0x4d, 0xd7, 0xc3, 0x43, 0xc5, 0xff, 0x48, 0xae, 0xc4, - 0x94, 0xdb, 0x9b, 0x56, 0xee, 0xcc, 0xb8, 0x82, 0xd0, 0xe8, 0xfb, 0x32, 0xf9, 0xa2, 0x3e, 0x05, - 0x01, 0xbf, 0x84, 0x08, 0xfd, 0xac, 0xf4, 0x8c, 0xe8, 0xe6, 0xb4, 0xd4, 0xef, 0x71, 0xcc, 0xd0, - 0x37, 0xa7, 0x3b, 0x79, 0xc1, 0xd8, 0xc6, 0x8c, 0xdc, 0x6f, 0x73, 0x7b, 0x72, 0x56, 0x7e, 0xe2, - 0x43, 0x95, 0x0f, 0xbb, 0x28, 0x5f, 0x21, 0xb7, 0x88, 0x61, 0x6d, 0x6e, 0xba, 0x00, 0xfe, 0x08, - 0x90, 0xf2, 0xda, 0x16, 0xeb, 0x32, 0x7e, 0xde, 0x40, 0xd5, 0x1a, 0x22, 0xfb, 0xd5, 0x07, 0x54, - 0x40, 0x1a, 0xe6, 0xca, 0xc3, 0xd1, 0x8c, 0x02, 0x10, 0x46, 0xcd, 0x03, 0x8d, 0xf5, 0x81, 0x55, - 0x37, 0x8d, 0x89, 0xd3, 0xd4, 0x78, 0x5b, 0xef, 0x7c, 0xe4, 0x23, 0x48, 0x24, 0x0f, 0xc0, 0x11, - 0xc6, 0x6e, 0x58, 0x97, 0xd5, 0x33, 0x3f, 0x30, 0xe3, 0x20, 0x66, 0x0d, 0x12, 0x42, 0xa1, 0x78, - 0xb0, 0xbf, 0x57, 0x2c, 0x94, 0x84, 0x42, 0xf1, 0xe8, 0xfb, 0xc3, 0xd2, 0xd1, 0x5e, 0xf1, 0xa0, - 0x90, 0xdf, 0x79, 0xba, 0x53, 0xf8, 0x26, 0xea, 0x63, 0xe7, 0x4e, 0xcf, 0x53, 0x11, 0x9b, 0x89, - 0xe1, 0x61, 0x61, 0x74, 0xc4, 0xde, 0xfe, 0xfe, 0x41, 0xd4, 0xcf, 0x4e, 0x9d, 0x9e, 0xa7, 0x42, - 0x7a, 0x9b, 0x49, 0xc3, 0xf2, 0x28, 0xa6, 0x78, 0x94, 0xcf, 0x17, 0x8a, 0xc5, 0x68, 0x80, 0x8d, - 0x9c, 0x9e, 0xa7, 0x26, 0x69, 0x97, 0x0d, 0xbd, 0xfc, 0x2d, 0xe9, 0xdb, 0x78, 0x33, 0x05, 0xc1, - 0x5d, 0xb5, 0xc6, 0xd4, 0x61, 0x6e, 0xf4, 0xa9, 0xee, 0xbc, 0xdc, 0xf1, 0x07, 0x33, 0x9b, 0xf5, - 0x08, 0xb4, 0x88, 0x3d, 0x81, 0xd9, 0x91, 0xf7, 0xf1, 0x03, 0x0f, 0x21, 0x0e, 0x95, 0x2e, 0x9b, - 0xf1, 0x86, 0x73, 0x99, 0x49, 0xbf, 0x02, 0x7b, 0x99, 0x69, 0x5b, 0xac, 0x7b, 0x9a, 0xc9, 0xf6, - 0x14, 0x60, 0x34, 0x60, 0x1c, 0x9e, 0x01, 0xab, 0x1e, 0xa2, 0x50, 0x2c, 0xbb, 0xe1, 0x1d, 0x6b, - 0xcd, 0x2a, 0x43, 0x74, 0xec, 0xb6, 0x9c, 0xbe, 0x22, 0x8e, 0x85, 0x64, 0xd7, 0xbc, 0x22, 0xad, - 0xf9, 0x9e, 0x43, 0xcc, 0xf1, 0x86, 0xeb, 0x25, 0x90, 0xb9, 0xce, 0xcd, 0x6b, 0x80, 0xad, 0x89, - 0x7f, 0x02, 0xb0, 0x5d, 0x03, 0x79, 0xb7, 0x10, 0x03, 0x0c, 0xbb, 0x7a, 0x35, 0xc6, 0x8a, 0x5e, - 0x84, 0x49, 0xf3, 0xc6, 0xc3, 0xb9, 0x0d, 0xa3, 0x00, 0xf6, 0xe1, 0x15, 0x00, 0x7b, 0xed, 0x8d, - 0x1c, 0xc6, 0x0f, 0xae, 0x18, 0x4a, 0x71, 0xee, 0xb5, 0xe7, 0x72, 0x80, 0xd4, 0x61, 0x6e, 0x74, - 0xd7, 0x77, 0xcd, 0x72, 0x04, 0xe8, 0xfe, 0xf1, 0xba, 0xec, 0x8a, 0xb9, 0xe2, 0xdb, 0x8b, 0xa4, - 0xff, 0xdd, 0x45, 0xd2, 0xff, 0xef, 0x45, 0xd2, 0xff, 0xea, 0x32, 0xe9, 0x7b, 0x77, 0x99, 0xf4, - 0xfd, 0x7d, 0x99, 0xf4, 0x1d, 0x3f, 0xa9, 0x49, 0xda, 0x49, 0xbb, 0x92, 0x11, 0x71, 0x33, 0x2b, - 0x62, 0xb5, 0x89, 0xd5, 0xac, 0x54, 0x11, 0x1f, 0xd5, 0x70, 0xb6, 0xb3, 0x99, 0x6d, 0xe2, 0x6a, - 0xbb, 0x81, 0x54, 0xe3, 0xaf, 0xe1, 0xda, 0xe3, 0x47, 0xe6, 0x8f, 0x43, 0xad, 0xdb, 0x42, 0x6a, - 0x25, 0x4c, 0x7e, 0x1a, 0x6e, 0xfe, 0x1f, 0x00, 0x00, 0xff, 0xff, 0xd5, 0x99, 0xe1, 0x0c, 0xc3, - 0x14, 0x00, 0x00, + // 1275 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0xcd, 0x6e, 0xdb, 0x46, + 0x10, 0xd6, 0x5f, 0x64, 0x67, 0xe4, 0xda, 0x32, 0x65, 0x3b, 0x32, 0x65, 0x8b, 0x2a, 0x0f, 0xb1, + 0xe1, 0xc2, 0x52, 0x6c, 0xa7, 0x28, 0x62, 0x14, 0x28, 0x2c, 0x55, 0x41, 0x8d, 0xd6, 0x3f, 0xa0, + 0xe4, 0x02, 0x75, 0x8b, 0x0a, 0x12, 0xb5, 0x91, 0x09, 0x49, 0x5c, 0x95, 0xa4, 0x94, 0xe8, 0x0d, + 0x02, 0x9f, 0x72, 0x0e, 0x60, 0x20, 0x45, 0x4f, 0x45, 0x0f, 0xe9, 0x63, 0xe4, 0x98, 0x53, 0x5b, + 0xf4, 0x20, 0x14, 0xf6, 0xa5, 0x67, 0x3d, 0x41, 0xc1, 0xe5, 0x92, 0xa2, 0x24, 0x12, 0xa6, 0x13, + 0xdb, 0xcd, 0x6d, 0x77, 0xe6, 0xdb, 0xd9, 0xd9, 0xef, 0x1b, 0xee, 0x0f, 0x61, 0x49, 0xaa, 0x88, + 0x19, 0x11, 0x2b, 0x28, 0x23, 0x9e, 0x94, 0x65, 0x19, 0x35, 0x32, 0x9d, 0x8d, 0x8c, 0xf6, 0x2c, + 0xdd, 0x52, 0xb0, 0x86, 0x99, 0x98, 0x54, 0x11, 0xd3, 0xba, 0x37, 0x4d, 0xbd, 0xe9, 0xce, 0x06, + 0x3b, 0x57, 0xc3, 0x35, 0x4c, 0xfc, 0x19, 0xbd, 0x65, 0x40, 0x59, 0x6e, 0x10, 0xa8, 0x21, 0x21, + 0x59, 0xd3, 0xe3, 0x18, 0x2d, 0x0a, 0xf8, 0xd8, 0x69, 0x26, 0x33, 0x2c, 0x81, 0xf0, 0x3f, 0xfb, + 0x81, 0xd9, 0x53, 0x6b, 0x39, 0xc3, 0x78, 0xd0, 0x42, 0xf2, 0xae, 0x2c, 0x69, 0xcc, 0x27, 0x30, + 0xd1, 0xc2, 0x8a, 0x56, 0x92, 0xaa, 0x71, 0x7f, 0xca, 0xbf, 0x7a, 0x37, 0xcb, 0xf4, 0x7b, 0xdc, + 0x74, 0xb7, 0xdc, 0x6c, 0x6c, 0xf3, 0xd4, 0xc1, 0x0b, 0x61, 0xbd, 0xb5, 0x5b, 0x65, 0x3e, 0x87, + 0x09, 0x1a, 0x34, 0x1e, 0x48, 0xf9, 0x57, 0x23, 0x9b, 0x4b, 0x69, 0x87, 0x45, 0xa4, 0xe9, 0x1c, + 0xd9, 0xd0, 0x9b, 0x1e, 0xe7, 0x13, 0xcc, 0x21, 0xcc, 0x02, 0x84, 0x55, 0xa9, 0x26, 0x23, 0x25, + 0x1e, 0xd4, 0x67, 0x12, 0x68, 0x6f, 0x7b, 0xf2, 0xf9, 0x2b, 0xce, 0xf7, 0xef, 0x2b, 0xce, 0xc7, + 0x0b, 0xc0, 0x8e, 0xa7, 0x28, 0x20, 0xb5, 0x85, 0x65, 0x15, 0x31, 0x0f, 0x01, 0x68, 0xa8, 0x41, + 0xb6, 0xf3, 0xfd, 0x1e, 0x37, 0x6b, 0x64, 0x3b, 0xf0, 0xf1, 0xc2, 0x5d, 0xda, 0xd9, 0xad, 0xf2, + 0x7f, 0x04, 0x61, 0x76, 0x38, 0x68, 0x51, 0xe9, 0x5e, 0x6d, 0xd9, 0xfb, 0x10, 0x6b, 0x29, 0xa8, + 0x23, 0xe1, 0xb6, 0x5a, 0xb2, 0x65, 0x10, 0x20, 0x03, 0x93, 0xfd, 0x1e, 0xc7, 0xd2, 0x81, 0xe3, + 0x20, 0x5e, 0x98, 0x35, 0xad, 0x39, 0x33, 0x25, 0x3b, 0x8d, 0xc1, 0xab, 0xd3, 0x28, 0xc0, 0x9c, + 0x88, 0xdb, 0xb2, 0x86, 0x94, 0x56, 0x59, 0xd1, 0xba, 0xa5, 0x0e, 0x52, 0x54, 0x09, 0xcb, 0xf1, + 0x10, 0x49, 0x87, 0xeb, 0xf7, 0xb8, 0x04, 0x25, 0xc4, 0x01, 0xc5, 0x0b, 0x31, 0xbb, 0xf9, 0x5b, + 0xc3, 0xaa, 0x53, 0xdb, 0x52, 0x30, 0x7e, 0x52, 0x92, 0x64, 0x49, 0x8b, 0xdf, 0x49, 0xf9, 0x57, + 0xa7, 0xec, 0xd4, 0x0e, 0x7c, 0xbc, 0x70, 0x97, 0x74, 0x48, 0xed, 0x1c, 0xc3, 0x94, 0xe1, 0x39, + 0x41, 0x52, 0xed, 0x44, 0x8b, 0x87, 0xc9, 0x62, 0x58, 0xdb, 0x62, 0x8c, 0x1a, 0xed, 0x6c, 0xa4, + 0xbf, 0x22, 0x88, 0x6c, 0x42, 0x5f, 0x4a, 0xbf, 0xc7, 0xc5, 0xec, 0x71, 0x8d, 0xd1, 0xbc, 0x10, + 0x21, 0x5d, 0x03, 0x69, 0x2b, 0x96, 0x09, 0x97, 0x62, 0x49, 0xc0, 0xe2, 0x98, 0xae, 0x66, 0xad, + 0xf0, 0x7f, 0x8e, 0xa9, 0xbe, 0x23, 0xd6, 0xaf, 0xa6, 0xfa, 0x70, 0xb9, 0x05, 0xbc, 0x95, 0x1b, + 0x73, 0x0c, 0xf7, 0x86, 0x78, 0xb7, 0x85, 0x20, 0x55, 0x9f, 0xe5, 0xfb, 0x3d, 0x2e, 0xe9, 0x20, + 0x90, 0x3d, 0xde, 0xbc, 0xdd, 0x33, 0xa8, 0x9b, 0x9b, 0x50, 0x7e, 0x03, 0x0c, 0x41, 0x4b, 0x9a, + 0xd2, 0xa5, 0xc2, 0xcf, 0xf5, 0x7b, 0x5c, 0xd4, 0x2e, 0x90, 0xa6, 0x74, 0x79, 0x61, 0x92, 0xb4, + 0xf5, 0x6f, 0xe7, 0x03, 0x93, 0x7d, 0x47, 0xac, 0x5b, 0xb2, 0xff, 0x16, 0x80, 0xf9, 0x61, 0x6f, + 0x0e, 0xcb, 0x4f, 0x24, 0xa5, 0x79, 0x1b, 0xd2, 0x5b, 0x54, 0x96, 0xc5, 0x3a, 0x11, 0xdb, 0x81, + 0xca, 0xb2, 0x58, 0x37, 0xa9, 0xd4, 0x0b, 0x72, 0x94, 0xca, 0xd0, 0x8d, 0x50, 0x79, 0xc7, 0x85, + 0x4a, 0x0e, 0x96, 0x1d, 0xc9, 0xb2, 0xe8, 0x7c, 0xe9, 0x87, 0xd8, 0x00, 0x91, 0x6b, 0x60, 0x15, + 0x5d, 0xfd, 0xd0, 0x78, 0x37, 0x32, 0x2f, 0x3f, 0x2c, 0x96, 0x21, 0xe1, 0x90, 0x9b, 0x95, 0xfb, + 0xeb, 0x00, 0x2c, 0x8c, 0xf8, 0x6f, 0xb1, 0x16, 0x86, 0x37, 0xd4, 0xe0, 0x3b, 0x6e, 0xa8, 0xb7, + 0x5b, 0x0e, 0x29, 0x48, 0x3a, 0x13, 0x66, 0x71, 0xfa, 0x22, 0x00, 0x1f, 0xed, 0xa9, 0x35, 0x01, + 0x89, 0x9d, 0xc3, 0xb2, 0x58, 0x47, 0x1a, 0xf3, 0x08, 0xc2, 0x2d, 0xd2, 0x22, 0x4c, 0x46, 0x36, + 0x13, 0x8e, 0x27, 0x99, 0x01, 0xa6, 0x07, 0x19, 0x1d, 0xc0, 0x3c, 0x86, 0xa8, 0x91, 0xae, 0x88, + 0x9b, 0x4d, 0x49, 0x6b, 0x22, 0x59, 0x23, 0xf4, 0x4e, 0x65, 0x13, 0xfd, 0x1e, 0x77, 0xcf, 0xbe, + 0xa0, 0x01, 0x82, 0x17, 0x66, 0x88, 0x29, 0x67, 0x59, 0xc6, 0x48, 0x0b, 0xde, 0x08, 0x69, 0x21, + 0x17, 0xd2, 0x7e, 0x24, 0x1b, 0xce, 0x80, 0x11, 0xeb, 0xb6, 0xf2, 0x05, 0x84, 0x15, 0xa4, 0xb6, + 0x1b, 0x06, 0x33, 0xd3, 0x9b, 0x2b, 0x8e, 0xcc, 0x98, 0x70, 0x81, 0x40, 0x8b, 0xdd, 0x16, 0x12, + 0xe8, 0xb0, 0xed, 0x90, 0x3e, 0x07, 0xff, 0x77, 0x00, 0x60, 0x4f, 0xad, 0x15, 0xa5, 0x26, 0xc2, + 0xed, 0xeb, 0xe1, 0xbb, 0x2d, 0x2b, 0x48, 0x44, 0x52, 0x07, 0x55, 0xdd, 0xf8, 0x1e, 0x20, 0x4c, + 0xbe, 0x8f, 0x2c, 0xcb, 0x8d, 0xf2, 0xfd, 0x35, 0x30, 0x32, 0x7a, 0xa6, 0x95, 0x54, 0xf4, 0x53, + 0x1b, 0xc9, 0x22, 0x2a, 0x29, 0x48, 0xec, 0x10, 0xee, 0x43, 0xd9, 0xe5, 0x7e, 0x8f, 0x5b, 0x34, + 0x22, 0x8c, 0x63, 0x78, 0x21, 0xaa, 0x1b, 0x0b, 0xd4, 0xa6, 0xeb, 0xe1, 0xa1, 0xe2, 0xbf, 0x27, + 0x57, 0x62, 0xca, 0xed, 0x75, 0x2b, 0xf7, 0xd2, 0xb8, 0x82, 0xd0, 0xe8, 0x07, 0x32, 0xf9, 0xa2, + 0x3e, 0x04, 0x01, 0x3f, 0x83, 0x08, 0xfd, 0xac, 0xf4, 0x8c, 0xe8, 0xe6, 0xb4, 0xd0, 0xef, 0x71, + 0xcc, 0xd0, 0x37, 0xa7, 0x3b, 0x79, 0xc1, 0xd8, 0xc6, 0x8c, 0xdc, 0x6f, 0x72, 0x7b, 0x72, 0x56, + 0xfe, 0xce, 0xfb, 0x2a, 0x1f, 0x76, 0x51, 0xbe, 0x42, 0x6e, 0x11, 0xc3, 0xda, 0x5c, 0x77, 0x01, + 0xfc, 0x1e, 0x20, 0xe5, 0xb5, 0x23, 0xd6, 0x65, 0xfc, 0xb4, 0x81, 0xaa, 0x35, 0x44, 0xf6, 0xab, + 0xf7, 0xa8, 0x80, 0x55, 0x98, 0x29, 0x0f, 0x47, 0x33, 0x0a, 0x40, 0x18, 0x35, 0x0f, 0x34, 0xd6, + 0x07, 0x56, 0xdd, 0x34, 0x26, 0x4e, 0x53, 0xe3, 0x1d, 0xbd, 0xf3, 0x3f, 0x1f, 0x41, 0x22, 0x79, + 0x00, 0x8e, 0x30, 0x76, 0xcd, 0xba, 0xac, 0xfd, 0xea, 0x07, 0x66, 0x1c, 0xc4, 0x7c, 0x0a, 0x29, + 0x21, 0x5f, 0x38, 0x3c, 0xd8, 0x2f, 0xe4, 0x4b, 0x42, 0xbe, 0x70, 0xf4, 0x4d, 0xb1, 0x54, 0xfc, + 0xee, 0x30, 0x5f, 0x3a, 0xda, 0x2f, 0x1c, 0xe6, 0x73, 0xbb, 0x8f, 0x77, 0xf3, 0x5f, 0x46, 0x7d, + 0xec, 0xcc, 0xe9, 0x59, 0x2a, 0x62, 0x33, 0x31, 0x2b, 0xb0, 0xe8, 0x38, 0x6c, 0xff, 0xe0, 0xe0, + 0x30, 0xea, 0x67, 0x27, 0x4f, 0xcf, 0x52, 0x21, 0xbd, 0xcd, 0xac, 0xc3, 0x92, 0x23, 0xb0, 0x70, + 0x94, 0xcb, 0xe5, 0x0b, 0x85, 0x68, 0x80, 0x8d, 0x9c, 0x9e, 0xa5, 0x26, 0x68, 0x97, 0x0d, 0x3d, + 0xff, 0x25, 0xe9, 0xdb, 0x7c, 0x3d, 0x09, 0xc1, 0x3d, 0xb5, 0xc6, 0xd4, 0x61, 0x66, 0xf4, 0xe5, + 0xee, 0xbc, 0xfa, 0xf1, 0xf7, 0x33, 0x9b, 0xf1, 0x08, 0xb4, 0x78, 0x3e, 0x81, 0xe9, 0x91, 0xe7, + 0xf2, 0x7d, 0x0f, 0x21, 0x8a, 0x4a, 0x97, 0x4d, 0x7b, 0xc3, 0xb9, 0xcc, 0xa4, 0xdf, 0x88, 0xbd, + 0xcc, 0xb4, 0x23, 0xd6, 0x3d, 0xcd, 0x64, 0x7b, 0x19, 0x30, 0x1a, 0x30, 0x0e, 0xaf, 0x82, 0x35, + 0x0f, 0x51, 0x28, 0x96, 0xdd, 0xf4, 0x8e, 0xb5, 0x66, 0x95, 0x21, 0x3a, 0x76, 0x79, 0x5e, 0xbd, + 0x24, 0x8e, 0x85, 0x64, 0x1f, 0x78, 0x45, 0x5a, 0xf3, 0x3d, 0x85, 0x98, 0xe3, 0x85, 0xd7, 0x4b, + 0x20, 0x73, 0x9d, 0x5b, 0x57, 0x00, 0x5b, 0x13, 0xff, 0x00, 0x60, 0xbb, 0x15, 0xf2, 0x6e, 0x21, + 0x06, 0x18, 0x76, 0xed, 0x72, 0x8c, 0x15, 0xbd, 0x00, 0x13, 0xe6, 0x05, 0x88, 0x73, 0x1b, 0x46, + 0x01, 0xec, 0xca, 0x25, 0x00, 0x7b, 0xed, 0x8d, 0x9c, 0xcd, 0xf7, 0x2f, 0x19, 0x4a, 0x71, 0xee, + 0xb5, 0xe7, 0x72, 0x9e, 0xd4, 0x61, 0x66, 0xf4, 0x10, 0x70, 0xcd, 0x72, 0x04, 0xe8, 0xfe, 0xf1, + 0xba, 0x6c, 0x92, 0xd9, 0xc2, 0x9b, 0xf3, 0xa4, 0xff, 0xed, 0x79, 0xd2, 0xff, 0xcf, 0x79, 0xd2, + 0xff, 0xe2, 0x22, 0xe9, 0x7b, 0x7b, 0x91, 0xf4, 0xfd, 0x75, 0x91, 0xf4, 0x1d, 0x3f, 0xaa, 0x49, + 0xda, 0x49, 0xbb, 0x92, 0x16, 0x71, 0x33, 0x23, 0x62, 0xb5, 0x89, 0xd5, 0x8c, 0x54, 0x11, 0xd7, + 0x6b, 0x38, 0xd3, 0xd9, 0xca, 0x34, 0x71, 0xb5, 0xdd, 0x40, 0xaa, 0xf1, 0x13, 0xf1, 0xc1, 0xc3, + 0x75, 0xf3, 0x3f, 0xa2, 0xd6, 0x6d, 0x21, 0xb5, 0x12, 0x26, 0xff, 0x10, 0xb7, 0xfe, 0x0b, 0x00, + 0x00, 0xff, 0xff, 0xec, 0xba, 0x10, 0x62, 0xd2, 0x14, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/proto/ibc/core/channel/v1/tx.proto b/proto/ibc/core/channel/v1/tx.proto index d4d6df1d5e9..15714173a1a 100644 --- a/proto/ibc/core/channel/v1/tx.proto +++ b/proto/ibc/core/channel/v1/tx.proto @@ -47,11 +47,11 @@ enum ResponseResultType { option (gogoproto.goproto_enum_prefix) = false; // Default zero value enumeration - RESPONSE_RESULT_UNSPECIFIED = 0 [(gogoproto.enumvalue_customname) = "UNSPECIFIED"]; + 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_NOOP = 1 [(gogoproto.enumvalue_customname) = "NOOP"]; + RESPONSE_RESULT_TYPE_NOOP = 1 [(gogoproto.enumvalue_customname) = "NOOP"]; // The message was executed successfully - RESPONSE_RESULT_SUCCESS = 2 [(gogoproto.enumvalue_customname) = "SUCCESS"]; + RESPONSE_RESULT_TYPE_SUCCESS = 2 [(gogoproto.enumvalue_customname) = "SUCCESS"]; } // MsgChannelOpenInit defines an sdk.Msg to initialize a channel handshake. It From 41a07331986d9bc7707782d9539e9ae7d24fb300 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 24 May 2022 20:36:11 +0200 Subject: [PATCH 076/140] feat: Add sender to fungible_token_packet events (backport #1414) (#1426) * Add Sender to funginble_token_packet events (cherry picked from commit f05a7cf8840ead622e7b5628466e1f9a311a717d) * chore: Updated CHANGELOG.md (cherry picked from commit c809c518423de21c576b8a80fec7fb5f5e07fca8) # Conflicts: # CHANGELOG.md * Update CHANGELOG.md (cherry picked from commit d8be3d0ce674c004d7543413cbeba3455d7028e2) # Conflicts: # CHANGELOG.md * Update CHANGELOG.md Co-authored-by: Damian Nolan (cherry picked from commit 46d73a09352d7db7b01d006f64627d519919f44e) # Conflicts: # CHANGELOG.md * fix conflicts Co-authored-by: chatton Co-authored-by: Cian Hatton Co-authored-by: Carlos Rodriguez --- CHANGELOG.md | 1 + modules/apps/transfer/ibc_module.go | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6880b9f6e4..c9fa888d6be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (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`. ### Features diff --git a/modules/apps/transfer/ibc_module.go b/modules/apps/transfer/ibc_module.go index f5ed807d8b2..f611c62642f 100644 --- a/modules/apps/transfer/ibc_module.go +++ b/modules/apps/transfer/ibc_module.go @@ -190,6 +190,7 @@ func (im IBCModule) OnRecvPacket( sdk.NewEvent( types.EventTypePacket, sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(sdk.AttributeKeySender, data.Sender), sdk.NewAttribute(types.AttributeKeyReceiver, data.Receiver), sdk.NewAttribute(types.AttributeKeyDenom, data.Denom), sdk.NewAttribute(types.AttributeKeyAmount, data.Amount), @@ -225,6 +226,7 @@ func (im IBCModule) OnAcknowledgementPacket( sdk.NewEvent( types.EventTypePacket, sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(sdk.AttributeKeySender, data.Sender), sdk.NewAttribute(types.AttributeKeyReceiver, data.Receiver), sdk.NewAttribute(types.AttributeKeyDenom, data.Denom), sdk.NewAttribute(types.AttributeKeyAmount, data.Amount), From 03376764e1dfbed10f509e9eedf1e199e1b157ea Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 24 May 2022 21:55:51 +0200 Subject: [PATCH 077/140] add swagger for interchain accounts (#1402) (#1410) ## 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. - [x] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [x] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit bf444a6f68c0c5ddfb446360e536a71ccc3ff83a) Co-authored-by: Carlos Rodriguez --- docs/client/config.json | 16 +++ docs/client/swagger-ui/swagger.yaml | 157 ++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) diff --git a/docs/client/config.json b/docs/client/config.json index cfc6dc7b9c4..210674dcc7c 100644 --- a/docs/client/config.json +++ b/docs/client/config.json @@ -14,6 +14,22 @@ } } }, + { + "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": { diff --git a/docs/client/swagger-ui/swagger.yaml b/docs/client/swagger-ui/swagger.yaml index 5feb43dd2a5..87d9da550d3 100644 --- a/docs/client/swagger-ui/swagger.yaml +++ b/docs/client/swagger-ui/swagger.yaml @@ -298,6 +298,105 @@ paths: format: byte 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 + format: 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 + format: 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/client/v1/params: get: summary: ClientParams queries all parameters of the ibc client. @@ -10000,6 +10099,64 @@ definitions: chain. description: QueryParamsResponse is the response type for the Query/Params RPC method. + ibc.applications.interchain_accounts.controller.v1.Params: + type: object + properties: + controller_enabled: + type: boolean + format: 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.QueryParamsResponse: + type: object + properties: + params: + description: params defines the parameters of the module. + type: object + properties: + controller_enabled: + type: boolean + format: 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 + format: 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 + format: 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: From c26c51ca6576daad7f75fa429d0a92623665e10e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 30 May 2022 13:29:39 +0200 Subject: [PATCH 078/140] add actual parameter example to denom-trace and denom-hash CLI queries (backport #1442) (#1462) * add actual parameter example to denom-trace and denom-hash CLI queries (#1442) (cherry picked from commit 8062d014956682b153f82e247d943310f5ff62a9) # Conflicts: # modules/apps/transfer/client/cli/query.go * fix conflict Co-authored-by: Carlos Rodriguez --- modules/apps/transfer/client/cli/query.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/apps/transfer/client/cli/query.go b/modules/apps/transfer/client/cli/query.go index 3239b154377..5b9269c05c7 100644 --- a/modules/apps/transfer/client/cli/query.go +++ b/modules/apps/transfer/client/cli/query.go @@ -17,7 +17,7 @@ func GetCmdQueryDenomTrace() *cobra.Command { Use: "denom-trace [hash]", Short: "Query the denom trace info from a given trace hash", Long: "Query the denom trace info from a given trace hash", - Example: fmt.Sprintf("%s query ibc-transfer denom-trace [hash]", version.AppName), + Example: fmt.Sprintf("%s query ibc-transfer denom-trace 27A6394C3F9FF9C9DCF5DFFADF9BB5FE9A37C7E92B006199894CF1824DF9AC7C", version.AppName), Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientQueryContext(cmd) @@ -142,7 +142,7 @@ func GetCmdQueryDenomHash() *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 [denom_trace]", version.AppName), + 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) From 968d6978076d826f4b3efff57bb9047be0422b84 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 2 Jun 2022 20:20:57 +0200 Subject: [PATCH 079/140] Emit channel close event on ordered channel close (backport #1464) (#1475) * Emit channel close event on ordered channel close (#1464) (cherry picked from commit 9ed5ca4121d52c8a99076360acf62e52cf7887ea) # Conflicts: # CHANGELOG.md * fix conflict Co-authored-by: Cian Hatton Co-authored-by: Carlos Rodriguez --- CHANGELOG.md | 1 + modules/core/04-channel/keeper/events.go | 15 +++++++++++++++ modules/core/04-channel/keeper/timeout.go | 4 ++++ modules/core/04-channel/types/events.go | 1 + 4 files changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9fa888d6be..138c037a573 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (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. ### Features diff --git a/modules/core/04-channel/keeper/events.go b/modules/core/04-channel/keeper/events.go index d299d6afeda..37f9cd6ed63 100644 --- a/modules/core/04-channel/keeper/events.go +++ b/modules/core/04-channel/keeper/events.go @@ -250,3 +250,18 @@ func EmitTimeoutPacketEvent(ctx sdk.Context, packet exported.PacketI, channel ty ), }) } + +// EmitChannelClosedEvent emits a channel closed event. +func EmitChannelClosedEvent(ctx sdk.Context, packet exported.PacketI, 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()), + ), + }) +} diff --git a/modules/core/04-channel/keeper/timeout.go b/modules/core/04-channel/keeper/timeout.go index 5a14ef85b6b..f29f1cca671 100644 --- a/modules/core/04-channel/keeper/timeout.go +++ b/modules/core/04-channel/keeper/timeout.go @@ -170,6 +170,10 @@ func (k Keeper) TimeoutExecuted( // emit an event marking that we have processed the timeout EmitTimeoutPacketEvent(ctx, packet, channel) + if channel.Ordering == types.ORDERED && channel.State == types.CLOSED { + EmitChannelClosedEvent(ctx, packet, channel) + } + return nil } diff --git a/modules/core/04-channel/types/events.go b/modules/core/04-channel/types/events.go index 4154b856c5d..863b6c231cd 100644 --- a/modules/core/04-channel/types/events.go +++ b/modules/core/04-channel/types/events.go @@ -47,6 +47,7 @@ var ( EventTypeChannelOpenConfirm = "channel_open_confirm" EventTypeChannelCloseInit = "channel_close_init" EventTypeChannelCloseConfirm = "channel_close_confirm" + EventTypeChannelClosed = "channel_close" AttributeValueCategory = fmt.Sprintf("%s_%s", host.ModuleName, SubModuleName) ) From d745ace0a709947ad118b4a38c12e68850843861 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 9 Jun 2022 11:50:27 +0200 Subject: [PATCH 080/140] chore: improve DenomTrace grpc (backport #1342) (#1503) * chore: improve DenomTrace grpc (#1342) * change DenomTrace grpc * update CHANGELOG.md * minor * minor * minor * minor * minor * minor * Update modules/apps/transfer/keeper/grpc_query_test.go Co-authored-by: Carlos Rodriguez * Update modules/apps/transfer/keeper/grpc_query_test.go Co-authored-by: Carlos Rodriguez * Update CHANGELOG.md Co-authored-by: Damian Nolan * use TrimPrefix() in DenomTrace() * update migration doc Co-authored-by: Carlos Rodriguez Co-authored-by: Damian Nolan (cherry picked from commit 23e7e7dcfbe6303e83fe1f6e3d984ade97fcbbd9) # Conflicts: # CHANGELOG.md # docs/ibc/proto-docs.md # docs/migrations/v3-to-v4.md # modules/apps/transfer/client/cli/query.go * fix conflicts Co-authored-by: khanh <50263489+catShaark@users.noreply.github.com> Co-authored-by: crodriguezvega --- CHANGELOG.md | 1 + docs/client/swagger-ui/swagger.yaml | 4 ++- docs/ibc/proto-docs.md | 2 +- docs/migrations/v3-to-v4.md | 31 +++++++++++++++++++ modules/apps/transfer/client/cli/query.go | 8 ++--- modules/apps/transfer/keeper/grpc_query.go | 6 ++-- .../apps/transfer/keeper/grpc_query_test.go | 31 +++++++++++++------ modules/apps/transfer/types/query.pb.go | 2 +- .../ibc/applications/transfer/v1/query.proto | 2 +- 9 files changed, 68 insertions(+), 19 deletions(-) create mode 100644 docs/migrations/v3-to-v4.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 138c037a573..2f18c993717 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### 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`. diff --git a/docs/client/swagger-ui/swagger.yaml b/docs/client/swagger-ui/swagger.yaml index 87d9da550d3..74f6ee85a9e 100644 --- a/docs/client/swagger-ui/swagger.yaml +++ b/docs/client/swagger-ui/swagger.yaml @@ -235,7 +235,9 @@ paths: format: byte parameters: - name: hash - description: hash (in hex format) of the denomination trace information. + description: >- + hash (in hex format) or denom (full denom with ibc prefix) of the + denomination trace information. in: path required: true type: string diff --git a/docs/ibc/proto-docs.md b/docs/ibc/proto-docs.md index abcc6597743..877cc37557c 100644 --- a/docs/ibc/proto-docs.md +++ b/docs/ibc/proto-docs.md @@ -636,7 +636,7 @@ method | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | -| `hash` | [string](#string) | | hash (in hex format) of the denomination trace information. | +| `hash` | [string](#string) | | hash (in hex format) or denom (full denom with ibc prefix) of the denomination trace information. | diff --git a/docs/migrations/v3-to-v4.md b/docs/migrations/v3-to-v4.md new file mode 100644 index 00000000000..5f684581ab2 --- /dev/null +++ b/docs/migrations/v3-to-v4.md @@ -0,0 +1,31 @@ +# 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 + +### IS04 - 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). + +## Relayers + +When using the `DenomTrace` gRPC, the full IBC denomination with the `ibc/` prefix may now be passed in. diff --git a/modules/apps/transfer/client/cli/query.go b/modules/apps/transfer/client/cli/query.go index 5b9269c05c7..2a6e3161a93 100644 --- a/modules/apps/transfer/client/cli/query.go +++ b/modules/apps/transfer/client/cli/query.go @@ -11,12 +11,12 @@ import ( "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" ) -// GetCmdQueryDenomTrace defines the command to query a a denomination trace from a given hash. +// GetCmdQueryDenomTrace defines the command to query a a denomination trace from a given trace hash or ibc denom. func GetCmdQueryDenomTrace() *cobra.Command { cmd := &cobra.Command{ - Use: "denom-trace [hash]", - Short: "Query the denom trace info from a given trace hash", - Long: "Query the denom trace info from a given trace hash", + Use: "denom-trace [hash/denom]", + Short: "Query the denom trace info from a given trace hash or ibc denom", + Long: "Query the denom trace info from a given trace hash or ibc denom", Example: fmt.Sprintf("%s query ibc-transfer denom-trace 27A6394C3F9FF9C9DCF5DFFADF9BB5FE9A37C7E92B006199894CF1824DF9AC7C", version.AppName), Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { diff --git a/modules/apps/transfer/keeper/grpc_query.go b/modules/apps/transfer/keeper/grpc_query.go index e0b16c6a761..5e7c5e7d295 100644 --- a/modules/apps/transfer/keeper/grpc_query.go +++ b/modules/apps/transfer/keeper/grpc_query.go @@ -3,6 +3,7 @@ package keeper import ( "context" "fmt" + "strings" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" @@ -22,9 +23,10 @@ func (q Keeper) DenomTrace(c context.Context, req *types.QueryDenomTraceRequest) return nil, status.Error(codes.InvalidArgument, "empty request") } - hash, err := types.ParseHexHash(req.Hash) + 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, %s", req.Hash, err)) + return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("invalid denom trace hash: %s, error: %s", hash.String(), err)) } ctx := sdk.UnwrapSDKContext(c) diff --git a/modules/apps/transfer/keeper/grpc_query_test.go b/modules/apps/transfer/keeper/grpc_query_test.go index 34563447b5f..963abd21577 100644 --- a/modules/apps/transfer/keeper/grpc_query_test.go +++ b/modules/apps/transfer/keeper/grpc_query_test.go @@ -21,37 +21,50 @@ func (suite *KeeperTestSuite) TestQueryDenomTrace() { expPass bool }{ { - "invalid hex hash", + "success: correct ibc denom", func() { + expTrace.Path = "transfer/channelToA/transfer/channelToB" + expTrace.BaseDenom = "uatom" + suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace(suite.chainA.GetContext(), expTrace) + req = &types.QueryDenomTraceRequest{ - Hash: "!@#!@#!", + Hash: expTrace.IBCDenom(), } }, - false, + true, }, { - "not found denom trace", + "success: correct hex hash", func() { expTrace.Path = "transfer/channelToA/transfer/channelToB" expTrace.BaseDenom = "uatom" + suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace(suite.chainA.GetContext(), expTrace) + req = &types.QueryDenomTraceRequest{ Hash: expTrace.Hash().String(), } }, + true, + }, + { + "failure: invalid hash", + func() { + req = &types.QueryDenomTraceRequest{ + Hash: "!@#!@#!", + } + }, false, }, { - "success", + "failure: not found denom trace", func() { expTrace.Path = "transfer/channelToA/transfer/channelToB" expTrace.BaseDenom = "uatom" - suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace(suite.chainA.GetContext(), expTrace) - req = &types.QueryDenomTraceRequest{ - Hash: expTrace.Hash().String(), + Hash: expTrace.IBCDenom(), } }, - true, + false, }, } diff --git a/modules/apps/transfer/types/query.pb.go b/modules/apps/transfer/types/query.pb.go index 024da758162..e1206ab0e30 100644 --- a/modules/apps/transfer/types/query.pb.go +++ b/modules/apps/transfer/types/query.pb.go @@ -33,7 +33,7 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // QueryDenomTraceRequest is the request type for the Query/DenomTrace RPC // method type QueryDenomTraceRequest struct { - // hash (in hex format) of the denomination trace information. + // hash (in hex format) or denom (full denom with ibc prefix) of the denomination trace information. Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` } diff --git a/proto/ibc/applications/transfer/v1/query.proto b/proto/ibc/applications/transfer/v1/query.proto index 2ed28049fd7..8491c52139b 100644 --- a/proto/ibc/applications/transfer/v1/query.proto +++ b/proto/ibc/applications/transfer/v1/query.proto @@ -35,7 +35,7 @@ service Query { // QueryDenomTraceRequest is the request type for the Query/DenomTrace RPC // method message QueryDenomTraceRequest { - // hash (in hex format) of the denomination trace information. + // hash (in hex format) or denom (full denom with ibc prefix) of the denomination trace information. string hash = 1; } From da89037c7af194a9279d65d26835f4e1da6b759f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 9 Jun 2022 14:28:53 +0200 Subject: [PATCH 081/140] Emit an event to indicate a successful acknowledgement in the ICA module (backport #1466) (#1508) * Emit an event to indicate a successful acknowledgement in the ICA module (#1466) (cherry picked from commit b2ca1932849cc3f05fe9e6ec30049aa4d08e6fc6) # Conflicts: # modules/apps/27-interchain-accounts/host/keeper/events.go * fix conflicts * Update events.go Co-authored-by: Cian Hatton Co-authored-by: crodriguezvega --- CHANGELOG.md | 1 + .../27-interchain-accounts/host/ibc_module.go | 11 ++++++----- .../host/keeper/events.go | 19 ++++++++++++++----- .../27-interchain-accounts/types/events.go | 1 + 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f18c993717..4ec428baae3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,6 +103,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (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. * (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. +* (interchain-accounts) [\#1466](https://github.com/cosmos/ibc-go/pull/1466) Emit event when there is an acknowledgement during `OnRecvPacket`. ### Features diff --git a/modules/apps/27-interchain-accounts/host/ibc_module.go b/modules/apps/27-interchain-accounts/host/ibc_module.go index fb403c71937..d750ea0a211 100644 --- a/modules/apps/27-interchain-accounts/host/ibc_module.go +++ b/modules/apps/27-interchain-accounts/host/ibc_module.go @@ -110,15 +110,16 @@ func (im IBCModule) OnRecvPacket( } txResponse, err := im.keeper.OnRecvPacket(ctx, packet) + ack := channeltypes.NewResultAcknowledgement(txResponse) if err != nil { - // Emit an event including the error msg - keeper.EmitWriteErrorAcknowledgementEvent(ctx, packet, err) - - return types.NewErrorAcknowledgement(err) + ack = types.NewErrorAcknowledgement(err) } + // 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 channeltypes.NewResultAcknowledgement(txResponse) + return ack } // OnAcknowledgementPacket implements the IBCModule interface diff --git a/modules/apps/27-interchain-accounts/host/keeper/events.go b/modules/apps/27-interchain-accounts/host/keeper/events.go index 5926781d5c2..81cdef73d10 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/events.go +++ b/modules/apps/27-interchain-accounts/host/keeper/events.go @@ -1,20 +1,29 @@ package keeper import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/cosmos/ibc-go/v3/modules/core/exported" + icatypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" ) -// EmitWriteErrorAcknowledgementEvent emits an event signalling an error acknowledgement and including the error details -func EmitWriteErrorAcknowledgementEvent(ctx sdk.Context, packet exported.PacketI, err error) { +// EmitAcknowledgementEvent emits an event signalling a successful or failed acknowledgement and including the error +// details if any. +func EmitAcknowledgementEvent(ctx sdk.Context, packet exported.PacketI, ack exported.Acknowledgement, err error) { + var errorMsg string + if err != nil { + errorMsg = err.Error() + } + ctx.EventManager().EmitEvent( sdk.NewEvent( icatypes.EventTypePacket, sdk.NewAttribute(sdk.AttributeKeyModule, icatypes.ModuleName), - sdk.NewAttribute(icatypes.AttributeKeyAckError, err.Error()), + sdk.NewAttribute(icatypes.AttributeKeyAckError, errorMsg), sdk.NewAttribute(icatypes.AttributeKeyHostChannelID, packet.GetDestChannel()), + sdk.NewAttribute(icatypes.AttributeKeyAckSuccess, fmt.Sprintf("%t", ack.Success())), ), ) } diff --git a/modules/apps/27-interchain-accounts/types/events.go b/modules/apps/27-interchain-accounts/types/events.go index 04882a6a644..9bfd1df3049 100644 --- a/modules/apps/27-interchain-accounts/types/events.go +++ b/modules/apps/27-interchain-accounts/types/events.go @@ -6,4 +6,5 @@ const ( AttributeKeyAckError = "error" AttributeKeyHostChannelID = "host_channel_id" + AttributeKeyAckSuccess = "success" ) From 184246470b6c60d49668e214810c6ca274fec899 Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Tue, 14 Jun 2022 14:08:53 +0200 Subject: [PATCH 082/140] Update CHANGELOG.md --- CHANGELOG.md | 131 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 127 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ec428baae3..5f58415edf2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,22 +38,30 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Dependencies -* [\#1300](https://github.com/cosmos/ibc-go/pull/1300) Bump SDK version to v0.45.4 - ### API Breaking ### State Machine Breaking ### Improvements +### Features + +### Bug Fixes + +## [v3.0.1](https://github.com/cosmos/ibc-go/releases/tag/v3.0.0) - 2022-04-16 + +### 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. -### Features - ### 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 @@ -118,12 +126,69 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (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.3.0](https://github.com/cosmos/ibc-go/releases/tag/v2.3.0) - 2022-04-16 + +### 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.1](https://github.com/cosmos/ibc-go/releases/tag/v2.2.1) - 2022-04-16 + +### 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.1](https://github.com/cosmos/ibc-go/releases/tag/v2.1.1) - 2022-04-16 + +### 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 @@ -192,12 +257,70 @@ Ref: https://keepachangelog.com/en/1.0.0/ * [\#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 From b84992c76172eea50dce39bd4ed55b1569198041 Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Tue, 14 Jun 2022 14:11:31 +0200 Subject: [PATCH 083/140] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f58415edf2..ffd02784118 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,7 +48,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Bug Fixes -## [v3.0.1](https://github.com/cosmos/ibc-go/releases/tag/v3.0.0) - 2022-04-16 +## [v3.0.1](https://github.com/cosmos/ibc-go/releases/tag/v3.0.1) - 2022-04-16 ### Dependencies From 23f555c951af79ce7b3dd7e1b233902efa955671 Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Tue, 14 Jun 2022 14:14:15 +0200 Subject: [PATCH 084/140] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ffd02784118..b044b39be70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -210,7 +210,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (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.2) - 2022-02-03 +## [v2.0.3](https://github.com/cosmos/ibc-go/releases/tag/v2.0.3) - 2022-02-03 ### Improvements From df58f1da1e3783a3f1cd39e11241680be7612c88 Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Tue, 14 Jun 2022 14:17:39 +0200 Subject: [PATCH 085/140] Update CHANGELOG.md --- CHANGELOG.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b044b39be70..f4cd273c0f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,20 +34,6 @@ Ref: https://keepachangelog.com/en/1.0.0/ # Changelog -## [Unreleased] - -### Dependencies - -### API Breaking - -### State Machine Breaking - -### Improvements - -### Features - -### Bug Fixes - ## [v3.0.1](https://github.com/cosmos/ibc-go/releases/tag/v3.0.1) - 2022-04-16 ### Dependencies From ae866d8b54dbc2a34629c04e3518e9723e46a4e6 Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Thu, 16 Jun 2022 10:45:29 +0200 Subject: [PATCH 086/140] Update versions --- docs/versions | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/versions b/docs/versions index e568f9a716f..2780084203d 100644 --- a/docs/versions +++ b/docs/versions @@ -1,7 +1,9 @@ release/v3.0.x v3.0.0 +release/v2.3.x v2.3.0 release/v2.2.x v2.2.0 release/v2.1.x v2.1.0 release/v2.0.x v2.0.0 +release/v1.5.x v1.5.0 release/v1.4.x v1.4.0 release/v1.3.x v1.3.0 release/v1.2.x v1.2.0 From 79eeb56454ee96ec89ba039c6df2e19060dbdda6 Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Mon, 20 Jun 2022 10:46:58 +0200 Subject: [PATCH 087/140] Delete v3-to-v4.md --- docs/migrations/v3-to-v4.md | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 docs/migrations/v3-to-v4.md diff --git a/docs/migrations/v3-to-v4.md b/docs/migrations/v3-to-v4.md deleted file mode 100644 index 5f684581ab2..00000000000 --- a/docs/migrations/v3-to-v4.md +++ /dev/null @@ -1,31 +0,0 @@ -# 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 - -### IS04 - 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). - -## Relayers - -When using the `DenomTrace` gRPC, the full IBC denomination with the `ibc/` prefix may now be passed in. From 53e15949d92156b0de0ece2a3b8a3cd08b7712e2 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 1 Jul 2022 10:33:54 +0200 Subject: [PATCH 088/140] docs: add upgrade client proposal event (#1596) (#1634) (cherry picked from commit 41282c772bd8494f9b48d9ebdb40be7d5f7abd18) Co-authored-by: Carlos Rodriguez --- docs/ibc/events.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/ibc/events.md b/docs/ibc/events.md index b7b28351cab..2c584302566 100644 --- a/docs/ibc/events.md +++ b/docs/ibc/events.md @@ -58,7 +58,12 @@ callbacks to IBC applications. | update_client_proposal | client_type | {clientType} | | update_client_proposal | consensus_height | {consensusHeight} | +### UpgradeProposal +| Type | Attribute Key | Attribute Value | +|-------------------------|-----------------|-------------------| +| upgrade_client_proposal | title | {title} | +| upgrade_client_proposal | height | {height} | ## ICS 03 - Connection From b91ef4e884c3243d7e5939cec5ac318db3197b31 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 5 Jul 2022 09:56:26 +0200 Subject: [PATCH 089/140] feat: emitting an event when handling a client upgrade proposal (backport #1570) (#1593) * feat: emitting an event when handling a client upgrade proposal (#1570) * feat: emitting an event when handling a client upgrade proposal * refactor: only emit event if err is nil * refactor: idiotmatic go: (cherry picked from commit 8422d0c4c35ef970539466c5bdec1cd27369bab3) # Conflicts: # CHANGELOG.md * Update CHANGELOG.md * Update CHANGELOG.md Co-authored-by: Sean King Co-authored-by: Damian Nolan --- CHANGELOG.md | 16 ++++++++++++++++ modules/core/02-client/keeper/events.go | 13 +++++++++++++ modules/core/02-client/keeper/proposal.go | 9 ++++++++- modules/core/02-client/types/events.go | 23 +++++++++++++---------- 4 files changed, 50 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4cd273c0f3..f92c45eac63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,22 @@ Ref: https://keepachangelog.com/en/1.0.0/ # Changelog +## [Unreleased] + +### Dependencies + +### API Breaking + +### State Machine Breaking + +### Improvements + +* (core/02-client) [\#1570](https://github.com/cosmos/ibc-go/pull/1570) Emitting an event when handling an upgrade client proposal. + +### Features + +### Bug Fixes + ## [v3.0.1](https://github.com/cosmos/ibc-go/releases/tag/v3.0.1) - 2022-04-16 ### Dependencies diff --git a/modules/core/02-client/keeper/events.go b/modules/core/02-client/keeper/events.go index ff8ae1c3acd..ad82292dc61 100644 --- a/modules/core/02-client/keeper/events.go +++ b/modules/core/02-client/keeper/events.go @@ -1,6 +1,8 @@ package keeper import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" @@ -68,6 +70,17 @@ func EmitUpdateClientProposalEvent(ctx sdk.Context, clientID string, clientState ) } +// EmitUpgradeClientProposalEvent emits an upgrade client proposal event +func EmitUpgradeClientProposalEvent(ctx sdk.Context, title string, height int64) { + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeUpgradeClientProposal, + sdk.NewAttribute(types.AttributeKeyUpgradePlanTitle, title), + sdk.NewAttribute(types.AttributeKeyUpgradePlanHeight, fmt.Sprintf("%d", height)), + ), + ) +} + // EmitSubmitMisbehaviourEvent emits a client misbehaviour event func EmitSubmitMisbehaviourEvent(ctx sdk.Context, clientID string, clientState exported.ClientState) { ctx.EventManager().EmitEvent( diff --git a/modules/core/02-client/keeper/proposal.go b/modules/core/02-client/keeper/proposal.go index ef0bf043e50..5e483d8a110 100644 --- a/modules/core/02-client/keeper/proposal.go +++ b/modules/core/02-client/keeper/proposal.go @@ -98,5 +98,12 @@ func (k Keeper) HandleUpgradeProposal(ctx sdk.Context, p *types.UpgradeProposal) // sets the new upgraded client in last height committed on this chain is at plan.Height, // since the chain will panic at plan.Height and new chain will resume at plan.Height - return k.upgradeKeeper.SetUpgradedClient(ctx, p.Plan.Height, bz) + if err = k.upgradeKeeper.SetUpgradedClient(ctx, p.Plan.Height, bz); err != nil { + return err + } + + // emitting an event for handling client upgrade proposal + EmitUpgradeClientProposalEvent(ctx, p.Title, p.Plan.Height) + + return nil } diff --git a/modules/core/02-client/types/events.go b/modules/core/02-client/types/events.go index 391e1e37080..93aa27afa0b 100644 --- a/modules/core/02-client/types/events.go +++ b/modules/core/02-client/types/events.go @@ -8,20 +8,23 @@ import ( // IBC client events const ( - AttributeKeyClientID = "client_id" - AttributeKeySubjectClientID = "subject_client_id" - AttributeKeyClientType = "client_type" - AttributeKeyConsensusHeight = "consensus_height" - AttributeKeyHeader = "header" + AttributeKeyClientID = "client_id" + AttributeKeySubjectClientID = "subject_client_id" + AttributeKeyClientType = "client_type" + AttributeKeyConsensusHeight = "consensus_height" + AttributeKeyHeader = "header" + AttributeKeyUpgradePlanTitle = "title" + AttributeKeyUpgradePlanHeight = "height" ) // IBC client events vars var ( - EventTypeCreateClient = "create_client" - EventTypeUpdateClient = "update_client" - EventTypeUpgradeClient = "upgrade_client" - EventTypeSubmitMisbehaviour = "client_misbehaviour" - EventTypeUpdateClientProposal = "update_client_proposal" + EventTypeCreateClient = "create_client" + EventTypeUpdateClient = "update_client" + EventTypeUpgradeClient = "upgrade_client" + EventTypeSubmitMisbehaviour = "client_misbehaviour" + EventTypeUpdateClientProposal = "update_client_proposal" + EventTypeUpgradeClientProposal = "upgrade_client_proposal" AttributeValueCategory = fmt.Sprintf("%s_%s", host.ModuleName, SubModuleName) ) From 2c986a0c7eca27abd4fab560a05254258a2e76fb Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 28 Jul 2022 10:33:41 +0200 Subject: [PATCH 090/140] fix broken link (#1776) (#1806) (cherry picked from commit ef7a5c72aa93bfa7290505788fbda455cd07b318) Co-authored-by: Carlos Rodriguez --- modules/apps/transfer/keeper/MBT_README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/apps/transfer/keeper/MBT_README.md b/modules/apps/transfer/keeper/MBT_README.md index 3cad1e58993..c2a62599f36 100644 --- a/modules/apps/transfer/keeper/MBT_README.md +++ b/modules/apps/transfer/keeper/MBT_README.md @@ -36,7 +36,7 @@ and executed automatically. The easiest way to run Apalache is by -[using a Docker image](https://github.com/informalsystems/apalache/blob/master/docs/manual.md#useDocker); +[using a Docker image](https://apalache.informal.systems/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`. From 93611dd121a83e4d913d5daaf7e5e7238e4b8ed0 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 29 Jul 2022 14:35:41 +0200 Subject: [PATCH 091/140] fix: add cosmos_proto.implements_interface (backport #1740) (#1815) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: add cosmos_proto.implements_interface (#1740) * fix: add cosmos_proto.implements_interface * changelog * Update CHANGELOG.md Co-authored-by: Carlos Rodriguez * run `make proto-all` * run `go mod tidy` Co-authored-by: Carlos Rodriguez Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> (cherry picked from commit 40d0ff7a2447b65f9401bb22547b2d397ce4d71e) # Conflicts: # CHANGELOG.md # modules/core/02-client/types/client.pb.go * fix conflicts Co-authored-by: Dan Lynch Co-authored-by: crodriguezvega --- CHANGELOG.md | 1 + modules/core/02-client/types/client.pb.go | 94 ++++++++++++----------- proto/ibc/core/client/v1/client.proto | 11 ++- 3 files changed, 56 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f92c45eac63..41418c70a07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### 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 ### Features diff --git a/modules/core/02-client/types/client.pb.go b/modules/core/02-client/types/client.pb.go index 34eb54cc90f..86bba961602 100644 --- a/modules/core/02-client/types/client.pb.go +++ b/modules/core/02-client/types/client.pb.go @@ -9,6 +9,7 @@ import ( types1 "github.com/cosmos/cosmos-sdk/x/upgrade/types" _ "github.com/gogo/protobuf/gogoproto" proto "github.com/gogo/protobuf/proto" + _ "github.com/regen-network/cosmos-proto" io "io" math "math" math_bits "math/bits" @@ -397,52 +398,53 @@ func init() { func init() { proto.RegisterFile("ibc/core/client/v1/client.proto", fileDescriptor_b6bc4c8185546947) } var fileDescriptor_b6bc4c8185546947 = []byte{ - // 705 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x54, 0x3f, 0x6f, 0xd3, 0x4e, - 0x18, 0x8e, 0xdb, 0xfc, 0xa2, 0xe6, 0x52, 0x35, 0xfd, 0xb9, 0x29, 0x0d, 0xa1, 0xca, 0x45, 0x27, - 0x86, 0x0c, 0xd4, 0x26, 0xa9, 0x84, 0xaa, 0x6c, 0x24, 0x4b, 0x3b, 0x80, 0x82, 0x51, 0x85, 0x60, - 0x89, 0xfc, 0xe7, 0xea, 0x5c, 0xe5, 0xf8, 0x22, 0xdf, 0x39, 0x90, 0x6f, 0xc0, 0xc8, 0xc8, 0xc0, - 0xd0, 0x6f, 0xc0, 0x97, 0x60, 0xe8, 0xd8, 0x91, 0xc9, 0x42, 0xed, 0xc2, 0x4a, 0x56, 0x16, 0x94, - 0xbb, 0x73, 0x1b, 0xf7, 0x0f, 0x42, 0xb0, 0xdd, 0x3d, 0xf7, 0xdc, 0x73, 0xcf, 0xf3, 0xda, 0xef, - 0x0b, 0x20, 0x71, 0x5c, 0xd3, 0xa5, 0x11, 0x36, 0xdd, 0x80, 0xe0, 0x90, 0x9b, 0x93, 0x96, 0x5a, - 0x19, 0xe3, 0x88, 0x72, 0xaa, 0xeb, 0xc4, 0x71, 0x8d, 0x39, 0xc1, 0x50, 0xf0, 0xa4, 0x55, 0xab, - 0xf8, 0xd4, 0xa7, 0xe2, 0xd8, 0x9c, 0xaf, 0x24, 0xb3, 0x76, 0xdf, 0xa7, 0xd4, 0x0f, 0xb0, 0x29, - 0x76, 0x4e, 0x7c, 0x64, 0xda, 0xe1, 0x54, 0x1d, 0x3d, 0x74, 0x29, 0x1b, 0x51, 0x66, 0xc6, 0x63, - 0x3f, 0xb2, 0x3d, 0x6c, 0x4e, 0x5a, 0x0e, 0xe6, 0x76, 0x2b, 0xdd, 0x4b, 0x16, 0xfa, 0xa4, 0x81, - 0xcd, 0x03, 0x0f, 0x87, 0x9c, 0x1c, 0x11, 0xec, 0xf5, 0xc4, 0x73, 0x2f, 0xb9, 0xcd, 0xb1, 0xde, - 0x02, 0x45, 0xf9, 0xfa, 0x80, 0x78, 0x55, 0xad, 0xa1, 0x35, 0x8b, 0xdd, 0xca, 0x2c, 0x81, 0xeb, - 0x53, 0x7b, 0x14, 0x74, 0xd0, 0xe5, 0x11, 0xb2, 0x56, 0xe4, 0xfa, 0xc0, 0xd3, 0xfb, 0x60, 0x55, - 0xe1, 0x6c, 0x2e, 0x51, 0x5d, 0x6a, 0x68, 0xcd, 0x52, 0xbb, 0x62, 0x48, 0x93, 0x46, 0x6a, 0xd2, - 0x78, 0x1a, 0x4e, 0xbb, 0x5b, 0xb3, 0x04, 0x6e, 0x64, 0xb4, 0xc4, 0x1d, 0x64, 0x95, 0xdc, 0x2b, - 0x13, 0xe8, 0xb3, 0x06, 0xaa, 0x3d, 0x1a, 0x32, 0x1c, 0xb2, 0x98, 0x09, 0xe8, 0x15, 0xe1, 0xc3, - 0x7d, 0x4c, 0xfc, 0x21, 0xd7, 0xf7, 0x40, 0x61, 0x28, 0x56, 0xc2, 0x5e, 0xa9, 0x5d, 0x33, 0x6e, - 0xd6, 0xcd, 0x90, 0xdc, 0x6e, 0xfe, 0x34, 0x81, 0x39, 0x4b, 0xf1, 0xf5, 0xd7, 0xa0, 0xec, 0xa6, - 0xaa, 0x7f, 0xe0, 0xb5, 0x36, 0x4b, 0xe0, 0x3d, 0xe5, 0x35, 0x7b, 0x0d, 0x59, 0x6b, 0x6e, 0xc6, - 0x1e, 0xfa, 0xa2, 0x81, 0x4d, 0x59, 0xc6, 0xac, 0x6f, 0xf6, 0x37, 0x05, 0x7d, 0x07, 0xd6, 0xaf, - 0x3d, 0xc8, 0xaa, 0x4b, 0x8d, 0xe5, 0x66, 0xa9, 0xfd, 0xe8, 0xb6, 0xac, 0x77, 0x55, 0xaa, 0x0b, - 0xe7, 0xe9, 0x67, 0x09, 0xdc, 0xba, 0x35, 0x04, 0x43, 0x56, 0x39, 0x9b, 0x82, 0xa1, 0x1f, 0x1a, - 0xa8, 0xc8, 0x18, 0x87, 0x63, 0xcf, 0xe6, 0xb8, 0x1f, 0xd1, 0x31, 0x65, 0x76, 0xa0, 0x57, 0xc0, - 0x7f, 0x9c, 0xf0, 0x00, 0xcb, 0x04, 0x96, 0xdc, 0xe8, 0x0d, 0x50, 0xf2, 0x30, 0x73, 0x23, 0x32, - 0xe6, 0x84, 0x86, 0xa2, 0x98, 0x45, 0x6b, 0x11, 0xd2, 0xf7, 0xc1, 0xff, 0x2c, 0x76, 0x8e, 0xb1, - 0xcb, 0x07, 0x57, 0x55, 0x58, 0x16, 0x55, 0xd8, 0x9e, 0x25, 0xb0, 0x2a, 0x9d, 0xdd, 0xa0, 0x20, - 0xab, 0xac, 0xb0, 0x5e, 0x5a, 0x94, 0x17, 0xa0, 0xc2, 0x62, 0x87, 0x71, 0xc2, 0x63, 0x8e, 0x17, - 0xc4, 0xf2, 0x42, 0x0c, 0xce, 0x12, 0xf8, 0xe0, 0x52, 0xec, 0x06, 0x0b, 0x59, 0xfa, 0x15, 0x9c, - 0x4a, 0x76, 0xf2, 0xef, 0x4f, 0x60, 0x0e, 0xfd, 0xd4, 0x40, 0xf9, 0x50, 0x76, 0xc7, 0x3f, 0xc7, - 0x7d, 0x02, 0xf2, 0xe3, 0xc0, 0x0e, 0x45, 0xc2, 0x52, 0x7b, 0xdb, 0x90, 0xcd, 0x68, 0xa4, 0xcd, - 0xa7, 0x9a, 0xd1, 0xe8, 0x07, 0x76, 0xa8, 0xfe, 0x4d, 0xc1, 0xd7, 0x8f, 0xc1, 0xa6, 0xe2, 0x78, - 0x83, 0x4c, 0x2f, 0xe5, 0x7f, 0xf3, 0x7f, 0x36, 0x66, 0x09, 0xdc, 0x96, 0x99, 0x6f, 0xbd, 0x8c, - 0xac, 0x8d, 0x14, 0x5f, 0xe8, 0xf0, 0xce, 0xea, 0x3c, 0xf5, 0xc7, 0x13, 0x98, 0xfb, 0x7e, 0x02, - 0xb5, 0xf9, 0x24, 0x28, 0xa8, 0xc6, 0xea, 0x81, 0x72, 0x84, 0x27, 0x84, 0x11, 0x1a, 0x0e, 0xc2, - 0x78, 0xe4, 0xe0, 0x48, 0xc4, 0xcf, 0x2f, 0x36, 0xc2, 0x35, 0x02, 0xb2, 0xd6, 0x52, 0xe4, 0xb9, - 0x00, 0x32, 0x22, 0xaa, 0x4d, 0x97, 0xee, 0x14, 0x91, 0x84, 0x05, 0x11, 0xe9, 0xa4, 0xb3, 0x92, - 0x5a, 0x44, 0xcf, 0x40, 0xa1, 0x6f, 0x47, 0xf6, 0x88, 0xcd, 0x85, 0xed, 0x20, 0xa0, 0x6f, 0x2f, - 0x43, 0xb2, 0xaa, 0xd6, 0x58, 0x6e, 0x16, 0x17, 0x85, 0xaf, 0x11, 0x90, 0xb5, 0xa6, 0x10, 0x99, - 0x9f, 0x75, 0xad, 0xd3, 0xf3, 0xba, 0x76, 0x76, 0x5e, 0xd7, 0xbe, 0x9d, 0xd7, 0xb5, 0x0f, 0x17, - 0xf5, 0xdc, 0xd9, 0x45, 0x3d, 0xf7, 0xf5, 0xa2, 0x9e, 0x7b, 0xb3, 0xe7, 0x13, 0x3e, 0x8c, 0x1d, - 0xc3, 0xa5, 0x23, 0x53, 0x8d, 0x50, 0xe2, 0xb8, 0x3b, 0x3e, 0x35, 0x27, 0xbb, 0xe6, 0x88, 0x7a, - 0x71, 0x80, 0x99, 0x9c, 0xde, 0x8f, 0xdb, 0x3b, 0x6a, 0x80, 0xf3, 0xe9, 0x18, 0x33, 0xa7, 0x20, - 0x3e, 0xca, 0xee, 0xaf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xb6, 0x6f, 0x16, 0x77, 0xe0, 0x05, 0x00, - 0x00, + // 734 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x55, 0xbd, 0x6e, 0x13, 0x4b, + 0x18, 0xf5, 0x3a, 0xbe, 0x56, 0x3c, 0xbe, 0x8a, 0x73, 0x37, 0xce, 0x8d, 0xaf, 0x6f, 0xe4, 0xb1, + 0x46, 0x14, 0x16, 0x22, 0xbb, 0xd8, 0x91, 0x50, 0xe4, 0x0e, 0xbb, 0x49, 0x0a, 0x90, 0x59, 0x14, + 0x21, 0x68, 0xac, 0xfd, 0x99, 0xac, 0x27, 0x5a, 0xef, 0x58, 0x3b, 0xb3, 0x06, 0xbf, 0x01, 0x1d, + 0x94, 0x20, 0xa5, 0xc8, 0x1b, 0xd0, 0xf0, 0x08, 0x14, 0x29, 0x23, 0x2a, 0xaa, 0x15, 0x4a, 0x1a, + 0x6a, 0x3f, 0x01, 0xf2, 0xcc, 0x6c, 0x62, 0xe7, 0x07, 0x10, 0x74, 0x33, 0x67, 0xce, 0x9e, 0x39, + 0xdf, 0xf1, 0x1e, 0x2f, 0x80, 0xc4, 0x71, 0x4d, 0x97, 0x46, 0xd8, 0x74, 0x03, 0x82, 0x43, 0x6e, + 0x8e, 0x9b, 0x6a, 0x65, 0x8c, 0x22, 0xca, 0xa9, 0xae, 0x13, 0xc7, 0x35, 0x66, 0x04, 0x43, 0xc1, + 0xe3, 0x66, 0xb5, 0xec, 0x53, 0x9f, 0x8a, 0x63, 0x73, 0xb6, 0x92, 0xcc, 0xea, 0x7f, 0x3e, 0xa5, + 0x7e, 0x80, 0x4d, 0xb1, 0x73, 0xe2, 0x03, 0xd3, 0x0e, 0x27, 0xea, 0xe8, 0x8e, 0x4b, 0xd9, 0x90, + 0x32, 0x33, 0x1e, 0xf9, 0x91, 0xed, 0x61, 0x73, 0xdc, 0x74, 0x30, 0xb7, 0x9b, 0xe9, 0x3e, 0x15, + 0x90, 0xac, 0xbe, 0x54, 0x96, 0x1b, 0x79, 0x84, 0x8e, 0x34, 0xb0, 0xbe, 0xe7, 0xe1, 0x90, 0x93, + 0x03, 0x82, 0xbd, 0xae, 0x70, 0xf2, 0x94, 0xdb, 0x1c, 0xeb, 0x4d, 0x50, 0x90, 0xc6, 0xfa, 0xc4, + 0xab, 0x68, 0x75, 0xad, 0x51, 0xe8, 0x94, 0xa7, 0x09, 0x5c, 0x9d, 0xd8, 0xc3, 0xa0, 0x8d, 0x2e, + 0x8e, 0x90, 0xb5, 0x2c, 0xd7, 0x7b, 0x9e, 0xde, 0x03, 0x7f, 0x2b, 0x9c, 0xcd, 0x24, 0x2a, 0xd9, + 0xba, 0xd6, 0x28, 0xb6, 0xca, 0x86, 0xf4, 0x6f, 0xa4, 0xfe, 0x8d, 0x87, 0xe1, 0xa4, 0xb3, 0x31, + 0x4d, 0xe0, 0xda, 0x82, 0x96, 0x78, 0x06, 0x59, 0x45, 0xf7, 0xd2, 0x04, 0xfa, 0xa0, 0x81, 0x4a, + 0x97, 0x86, 0x0c, 0x87, 0x2c, 0x66, 0x02, 0x7a, 0x46, 0xf8, 0x60, 0x17, 0x13, 0x7f, 0xc0, 0xf5, + 0x1d, 0x90, 0x1f, 0x88, 0x95, 0xb0, 0x57, 0x6c, 0x55, 0x8d, 0xeb, 0x91, 0x1a, 0x92, 0xdb, 0xc9, + 0x9d, 0x24, 0x30, 0x63, 0x29, 0xbe, 0xfe, 0x1c, 0x94, 0xdc, 0x54, 0xf5, 0x17, 0xbc, 0x56, 0xa7, + 0x09, 0xfc, 0x57, 0x79, 0x5d, 0x7c, 0x0c, 0x59, 0x2b, 0xee, 0x82, 0x3d, 0xf4, 0x49, 0x03, 0xeb, + 0x32, 0xc6, 0x45, 0xdf, 0xec, 0x77, 0x02, 0x7d, 0x05, 0x56, 0xaf, 0x5c, 0xc8, 0x2a, 0xd9, 0xfa, + 0x52, 0xa3, 0xd8, 0xba, 0x77, 0xd3, 0xac, 0xb7, 0x25, 0xd5, 0x81, 0xb3, 0xe9, 0xa7, 0x09, 0xdc, + 0xb8, 0x71, 0x08, 0x86, 0xac, 0xd2, 0xe2, 0x14, 0x0c, 0xbd, 0xc9, 0x82, 0xb2, 0x1c, 0x63, 0x7f, + 0xe4, 0xd9, 0x1c, 0xf7, 0x22, 0x3a, 0xa2, 0xcc, 0x0e, 0xf4, 0x32, 0xf8, 0x8b, 0x13, 0x1e, 0x60, + 0x39, 0x81, 0x25, 0x37, 0x7a, 0x1d, 0x14, 0x3d, 0xcc, 0xdc, 0x88, 0x8c, 0x38, 0xa1, 0xa1, 0x08, + 0xb3, 0x60, 0xcd, 0x43, 0xfa, 0x2e, 0xf8, 0x87, 0xc5, 0xce, 0x21, 0x76, 0x79, 0xff, 0x32, 0x85, + 0x25, 0x91, 0xc2, 0xe6, 0x34, 0x81, 0x15, 0xe9, 0xec, 0x1a, 0x05, 0x59, 0x25, 0x85, 0x75, 0xd3, + 0x50, 0x9e, 0x80, 0x32, 0x8b, 0x1d, 0xc6, 0x09, 0x8f, 0x39, 0x9e, 0x13, 0xcb, 0x09, 0x31, 0x38, + 0x4d, 0xe0, 0xff, 0x17, 0x62, 0xd7, 0x58, 0xc8, 0xd2, 0x2f, 0xe1, 0x54, 0xb2, 0x8d, 0x5e, 0x1f, + 0xc3, 0xcc, 0xe7, 0x8f, 0x5b, 0x55, 0xd5, 0x0d, 0x9f, 0x8e, 0x0d, 0x55, 0xa5, 0x59, 0xa8, 0x1c, + 0x87, 0x1c, 0xbd, 0xcf, 0x82, 0xd2, 0xbe, 0xac, 0xd5, 0x1f, 0x87, 0xf1, 0x00, 0xe4, 0x46, 0x81, + 0x1d, 0x8a, 0xf9, 0x8b, 0xad, 0x4d, 0x43, 0x5d, 0x9b, 0xb6, 0x36, 0xbd, 0xba, 0x17, 0xd8, 0xa1, + 0x7a, 0x73, 0x05, 0x5f, 0x3f, 0x04, 0xeb, 0x8a, 0xe3, 0xf5, 0x17, 0x9a, 0x96, 0xfb, 0xc1, 0xdb, + 0x5b, 0x9f, 0x26, 0x70, 0x53, 0x26, 0x72, 0xe3, 0xc3, 0xc8, 0x5a, 0x4b, 0xf1, 0xb9, 0xfe, 0xb7, + 0xef, 0xce, 0x32, 0x79, 0x77, 0x0c, 0x33, 0xdf, 0x8e, 0xa1, 0xf6, 0x93, 0x6c, 0x8e, 0x34, 0x90, + 0x57, 0xa5, 0xec, 0x82, 0x52, 0x84, 0xc7, 0x84, 0x11, 0x1a, 0xf6, 0xc3, 0x78, 0xe8, 0xe0, 0x48, + 0x84, 0x93, 0x9b, 0x2f, 0xd1, 0x15, 0x02, 0xb2, 0x56, 0x52, 0xe4, 0xb1, 0x00, 0x16, 0x44, 0x54, + 0xc5, 0xb3, 0xb7, 0x8a, 0x48, 0xc2, 0x9c, 0x88, 0x74, 0xd2, 0x5e, 0x4e, 0x07, 0x40, 0x8f, 0x40, + 0xbe, 0x67, 0x47, 0xf6, 0x90, 0xcd, 0x84, 0xed, 0x20, 0xa0, 0x2f, 0x2f, 0x22, 0x60, 0x15, 0xad, + 0xbe, 0xd4, 0x28, 0xcc, 0x0b, 0x5f, 0x21, 0x20, 0x6b, 0x45, 0x21, 0x32, 0x1d, 0xd6, 0xb1, 0x4e, + 0xce, 0x6a, 0xda, 0xe9, 0x59, 0x4d, 0xfb, 0x7a, 0x56, 0xd3, 0xde, 0x9e, 0xd7, 0x32, 0xa7, 0xe7, + 0xb5, 0xcc, 0x97, 0xf3, 0x5a, 0xe6, 0xc5, 0x8e, 0x4f, 0xf8, 0x20, 0x76, 0x0c, 0x97, 0x0e, 0xd5, + 0xdf, 0xac, 0x49, 0x1c, 0x77, 0xcb, 0xa7, 0xe6, 0x78, 0xdb, 0x1c, 0x52, 0x2f, 0x0e, 0x30, 0x93, + 0x1f, 0x85, 0xfb, 0xad, 0x2d, 0xf5, 0x5d, 0xe0, 0x93, 0x11, 0x66, 0x4e, 0x5e, 0xfc, 0x64, 0xdb, + 0xdf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x08, 0xd1, 0x44, 0x6d, 0x37, 0x06, 0x00, 0x00, } func (this *UpgradeProposal) Equal(that interface{}) bool { diff --git a/proto/ibc/core/client/v1/client.proto b/proto/ibc/core/client/v1/client.proto index 657d99ed204..f97263c4fcf 100644 --- a/proto/ibc/core/client/v1/client.proto +++ b/proto/ibc/core/client/v1/client.proto @@ -7,6 +7,7 @@ option go_package = "github.com/cosmos/ibc-go/v3/modules/core/02-client/types"; import "gogoproto/gogo.proto"; import "google/protobuf/any.proto"; import "cosmos/upgrade/v1beta1/upgrade.proto"; +import "cosmos_proto/cosmos.proto"; // IdentifiedClientState defines a client state with an additional client // identifier field. @@ -41,7 +42,8 @@ message ClientConsensusStates { // 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). message ClientUpdateProposal { - option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_getters) = false; + option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; // the title of the update proposal string title = 1; // the description of the proposal @@ -56,9 +58,10 @@ message ClientUpdateProposal { // UpgradeProposal is a gov Content type for initiating an IBC breaking // upgrade. message UpgradeProposal { - option (gogoproto.goproto_getters) = false; - option (gogoproto.goproto_stringer) = false; - option (gogoproto.equal) = true; + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = false; + option (gogoproto.equal) = true; + option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; string title = 1; string description = 2; From f0c3d3ac14ab93e7a11b93038f8ef54b760e3012 Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Tue, 2 Aug 2022 13:07:37 +0200 Subject: [PATCH 092/140] Update CHANGELOG.md --- CHANGELOG.md | 45 ++++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41418c70a07..209e4606b02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,24 +34,14 @@ Ref: https://keepachangelog.com/en/1.0.0/ # Changelog -## [Unreleased] - -### Dependencies - -### API Breaking - -### State Machine Breaking +## [v3.0.2](https://github.com/cosmos/ibc-go/releases/tag/v3.0.1) - 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 -### Features - -### Bug Fixes - -## [v3.0.1](https://github.com/cosmos/ibc-go/releases/tag/v3.0.1) - 2022-04-16 +## [v3.0.1](https://github.com/cosmos/ibc-go/releases/tag/v3.0.1) - 2022-06-14 ### Dependencies @@ -129,7 +119,18 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (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.3.0](https://github.com/cosmos/ibc-go/releases/tag/v2.3.0) - 2022-04-16 +## [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 @@ -155,7 +156,14 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (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.1](https://github.com/cosmos/ibc-go/releases/tag/v2.2.1) - 2022-04-16 +## [v2.2.2](https://github.com/cosmos/ibc-go/releases/tag/v2.2.1) - 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 @@ -175,7 +183,14 @@ Ref: https://keepachangelog.com/en/1.0.0/ * [\#851](https://github.com/cosmos/ibc-go/pull/851) Bump SDK version to v0.45.1 -## [v2.1.1](https://github.com/cosmos/ibc-go/releases/tag/v2.1.1) - 2022-04-16 +## [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 From 79a4af50c68bcc6e57a34d51bef97c9fa6f36692 Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Tue, 2 Aug 2022 14:28:14 +0200 Subject: [PATCH 093/140] Update CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 209e4606b02..301b54c2b7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ # Changelog -## [v3.0.2](https://github.com/cosmos/ibc-go/releases/tag/v3.0.1) - 2022-08-02 +## [v3.0.2](https://github.com/cosmos/ibc-go/releases/tag/v3.0.2) - 2022-08-02 ### Improvements @@ -156,7 +156,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (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.1) - 2022-08-02 +## [v2.2.2](https://github.com/cosmos/ibc-go/releases/tag/v2.2.2) - 2022-08-02 ### Improvements From 3a514ef7eacb0e188f5a0f1aa2656f2b16ca412f Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Tue, 2 Aug 2022 14:45:39 +0200 Subject: [PATCH 094/140] Update CHANGELOG.md --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 301b54c2b7a..7be5a1ecb51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,20 @@ Ref: https://keepachangelog.com/en/1.0.0/ # Changelog +## [Unreleased] + +### Dependencies + +### API Breaking + +### State Machine Breaking + +### Improvements + +### Features + +### Bug Fixes + ## [v3.0.2](https://github.com/cosmos/ibc-go/releases/tag/v3.0.2) - 2022-08-02 ### Improvements From 753380733da494c7178209b628797049217741d4 Mon Sep 17 00:00:00 2001 From: Cian Hatton Date: Tue, 9 Aug 2022 12:46:11 +0100 Subject: [PATCH 095/140] chore: adding dockerfile and release task to release/3.0.x (#1928) --- .github/workflows/release.yml | 25 +++++++++++++++++++++++++ Dockerfile | 23 +++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 Dockerfile diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8397348098c..f1da879c762 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,6 +6,10 @@ on: - 'v[0-9]+.[0-9]+.[0-9]+' # Push events to matching v*, i.e. v1.0.0, v20.15.10 - 'v[0-9]+.[0-9]+.[0-9]+-?[a-z0-9]+' # Push events to matching v*-[alpha/beta/rc], i.e. v3.0.0-alpha1, v3.0.0-beta1, v3.0.0-rc0 +env: + REGISTRY: ghcr.io + IMAGE_NAME: ibc-go-simd + jobs: goreleaser: runs-on: ubuntu-latest @@ -27,3 +31,24 @@ jobs: args: release --rm-dist env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to the Container registry + uses: docker/login-action@49ed152c8eca782a232dede0303416e8f356c37b + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@69f6fc9d46f2f8bf0d5491e4aabe0bb8c6a4678a + with: + images: ${{ env.REGISTRY }}/cosmos/${{ env.IMAGE_NAME }} + + - name: Build and push Docker image + uses: docker/build-push-action@1cb9d22b932e4832bb29793b7777ec860fc1cde0 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000000..a193f54906e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM golang:1.18 as builder + +ENV GOPATH="" +ENV GOMODULE="on" + +COPY go.mod . +COPY go.sum . + +RUN go mod download + +ADD testing testing +ADD modules modules +ADD LICENSE LICENSE + +COPY Makefile . + +RUN make build + +FROM ubuntu:20.04 + +COPY --from=builder /go/build/simd /bin/simd + +ENTRYPOINT ["simd"] From 0ec1fcc2a67732eda086d1c6b8c57dbc4a577c62 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 11 Aug 2022 15:52:25 +0100 Subject: [PATCH 096/140] Fixing github action workflows (backport #1428) (#1937) --- .github/workflows/check-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-docs.yml b/.github/workflows/check-docs.yml index abcaa066e2d..e5940c495f0 100644 --- a/.github/workflows/check-docs.yml +++ b/.github/workflows/check-docs.yml @@ -3,7 +3,7 @@ name: Check docs build # This will check if the docs build successfully by running `npm run build` on: pull_request: - paths: + paths: - './docs' jobs: From 5c407ab377d8e6b000b0ff41937e12a4665a83c4 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 12 Aug 2022 11:07:45 +0200 Subject: [PATCH 097/140] fix: "acknowledgement written" logs unsupported type (backport #1919) (#1958) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: "acknowledgement written" logs unsupported type (#1919) * fix: "acknowledgement written" logs unsupported type * Updating CHANGELOG.md Co-authored-by: Carlos Rodriguez Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> (cherry picked from commit 897e7ebc5c7a1c2178691fa281996b19997f1cd5) # Conflicts: # CHANGELOG.md * fix conflict Co-authored-by: Joe Abbey Co-authored-by: Carlos Rodriguez --- CHANGELOG.md | 2 ++ modules/core/04-channel/keeper/packet.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7be5a1ecb51..03df89bf330 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,8 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### 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.0.2](https://github.com/cosmos/ibc-go/releases/tag/v3.0.2) - 2022-08-02 ### Improvements diff --git a/modules/core/04-channel/keeper/packet.go b/modules/core/04-channel/keeper/packet.go index c3e8e45b89c..6d36285cca6 100644 --- a/modules/core/04-channel/keeper/packet.go +++ b/modules/core/04-channel/keeper/packet.go @@ -361,7 +361,7 @@ func (k Keeper) WriteAcknowledgement( // log that a packet acknowledgement has been written k.Logger(ctx).Info( "acknowledgement written", - "sequence", packet.GetSequence(), + "sequence", strconv.FormatUint(packet.GetSequence(), 10), "src_port", packet.GetSourcePort(), "src_channel", packet.GetSourceChannel(), "dst_port", packet.GetDestPort(), From 02b55a3c1466c86d90244fe42bf6453cd03703bb Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 23 Aug 2022 22:18:49 +0200 Subject: [PATCH 098/140] fix broken link (#2059) (#2094) (cherry picked from commit 7d26a87c96486b8961d485ce08e0748934871914) Co-authored-by: Carlos Rodriguez --- docs/ibc/relayer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ibc/relayer.md b/docs/ibc/relayer.md index ce3fabe252d..1cfec7ab650 100644 --- a/docs/ibc/relayer.md +++ b/docs/ibc/relayer.md @@ -27,7 +27,7 @@ a module event emission with the attribute value `ibc_` (02-clien ### Subscribing with Tendermint -Calling the Tendermint RPC method `Subscribe` via [Tendermint's Websocket](https://docs.tendermint.com/v0.35/rpc/) will return events using +Calling the Tendermint RPC method `Subscribe` via [Tendermint's Websocket](https://docs.tendermint.com/main/rpc/) 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 From 3252455e54d3a19a8a9234a1beb3e662a3fca2f8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 7 Sep 2022 13:07:16 +0200 Subject: [PATCH 099/140] chore: fix broken link to bank module spec (backport #2201) (#2206) * chore: fix broken link to bank module spec (#2201) ## 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. - [x] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [x] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit 1a0918d2d116f9fee8c83a037000d425d638ddd4) # Conflicts: # docs/apps/transfer/params.md * fix conflict Co-authored-by: Carlos Rodriguez --- modules/apps/transfer/spec/07_params.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/apps/transfer/spec/07_params.md b/modules/apps/transfer/spec/07_params.md index 142091a9433..2605f06849c 100644 --- a/modules/apps/transfer/spec/07_params.md +++ b/modules/apps/transfer/spec/07_params.md @@ -16,13 +16,11 @@ The ibc-transfer module contains the following parameters: 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 set the bank module's [`SendEnabled` parameter](https://github.com/cosmos/cosmos-sdk/blob/master/x/bank/spec/05_params.md#sendenabled) for the denomination to `false`. +To prevent a single token from being transferred from the chain, set the `SendEnabled` parameter to `true` and then, 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`. ## 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 set the bank module's [`SendEnabled` parameter](https://github.com/cosmos/cosmos-sdk/blob/master/x/bank/spec/05_params.md#sendenabled) for the denomination to `false`. +To prevent a single token from being transferred to the chain, set the `ReceiveEnabled` parameter to `true` and then, 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`. From c87a53dbf8a2b93908b88ec31b6417ec9bf2ed19 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 21 Sep 2022 11:13:34 +0200 Subject: [PATCH 100/140] fix broken link/update link (#2338) (#2343) (cherry picked from commit d908b1b445dc52563501d0c4d6d55319c166576f) Co-authored-by: Carlos Rodriguez --- docs/ibc/relayer.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ibc/relayer.md b/docs/ibc/relayer.md index 1cfec7ab650..6705ac6aa0a 100644 --- a/docs/ibc/relayer.md +++ b/docs/ibc/relayer.md @@ -42,5 +42,5 @@ piece of information needed to relay a packet. ## Example Implementations -- [Golang Relayer](https://github.com/iqlusioninc/relayer) -- [Hermes](https://github.com/informalsystems/ibc-rs/tree/master/relayer) +- [Golang Relayer](https://github.com/cosmos/relayer) +- [Hermes](https://github.com/informalsystems/ibc-rs/tree/master/crates/relayer) From 773e22b97dd095ef29b64b8aa029ac9bf1eb9536 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 21 Sep 2022 11:32:32 +0200 Subject: [PATCH 101/140] Fix ICAControllerKeeper (#2303) (#2329) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Carlos Rodriguez (cherry picked from commit 280db0ce2c4b8c19ab196c72b547d2e7f02b5bd8) Co-authored-by: Raul Bernal Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> --- docs/apps/interchain-accounts/integration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/apps/interchain-accounts/integration.md b/docs/apps/interchain-accounts/integration.md index 14757ccca3c..8d8ef554e15 100644 --- a/docs/apps/interchain-accounts/integration.md +++ b/docs/apps/interchain-accounts/integration.md @@ -76,7 +76,7 @@ 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.AccountKeeper, scopedICAControllerKeeper, app.MsgServiceRouter(), + scopedICAControllerKeeper, app.MsgServiceRouter(), ) app.ICAHostKeeper = icahostkeeper.NewKeeper( appCodec, keys[icahosttypes.StoreKey], app.GetSubspace(icahosttypes.SubModuleName), From 4ae28341dbd039e30274060459fa8a84631bd2af Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 21 Sep 2022 11:41:16 +0200 Subject: [PATCH 102/140] Adding `paramsKeeper.Subspace(icahosttypes.SubModuleName)` (#2220) (#2321) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adding `paramsKeeper.Subspace(icahosttypes.SubModuleName)` Adding `paramsKeeper.Subspace(icahosttypes.SubModuleName)` at `initParamsKeeper` func * fix spaces * adding missing paramsKeeper paramsKeeper.Subspace(icacontrollertypes.SubModuleName) Co-authored-by: Carlos Rodriguez Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> (cherry picked from commit 7b26bdaea4802329b4c14e37dae17e3d5dba20b5) Co-authored-by: Raul Bernal Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> --- docs/apps/interchain-accounts/integration.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/apps/interchain-accounts/integration.md b/docs/apps/interchain-accounts/integration.md index 8d8ef554e15..eca5ab47eab 100644 --- a/docs/apps/interchain-accounts/integration.md +++ b/docs/apps/interchain-accounts/integration.md @@ -122,6 +122,13 @@ app.mm.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 From 466e42884af41194f2542656bad7c6d79a3b3891 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 21 Sep 2022 10:44:46 +0000 Subject: [PATCH 103/140] fix: add nil checks for controller and host keeper services (backport #2308) (#2310) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: add nil checks for controller and host keeper services (#2308) ## 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. - [ ] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [ ] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit 888c4a0117bfa9753ac4b1ffd1ea81c09867e335) # Conflicts: # CHANGELOG.md # modules/apps/27-interchain-accounts/controller/keeper/migrations.go # modules/apps/27-interchain-accounts/module.go * fix merge conflicts Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> --- CHANGELOG.md | 1 + modules/apps/27-interchain-accounts/module.go | 20 +++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03df89bf330..eef5b7ecbad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### 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. * (modules/core/04-channel)[\#1919](https://github.com/cosmos/ibc-go/pull/1919) Fixed formatting of sequence for packet "acknowledgement written" logs. ## [v3.0.2](https://github.com/cosmos/ibc-go/releases/tag/v3.0.2) - 2022-08-02 diff --git a/modules/apps/27-interchain-accounts/module.go b/modules/apps/27-interchain-accounts/module.go index 1bb870fca5d..672ac130d96 100644 --- a/modules/apps/27-interchain-accounts/module.go +++ b/modules/apps/27-interchain-accounts/module.go @@ -73,8 +73,15 @@ func (AppModuleBasic) RegisterRESTRoutes(ctx client.Context, rtr *mux.Router) { // RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the interchain accounts module. func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { - controllertypes.RegisterQueryHandlerClient(context.Background(), mux, controllertypes.NewQueryClient(clientCtx)) - hosttypes.RegisterQueryHandlerClient(context.Background(), mux, hosttypes.NewQueryClient(clientCtx)) + 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 @@ -145,8 +152,13 @@ func (am AppModule) LegacyQuerierHandler(legacyQuerierCdc *codec.LegacyAmino) sd // RegisterServices registers module services func (am AppModule) RegisterServices(cfg module.Configurator) { - controllertypes.RegisterQueryServer(cfg.QueryServer(), am.controllerKeeper) - hosttypes.RegisterQueryServer(cfg.QueryServer(), am.hostKeeper) + if am.controllerKeeper != nil { + controllertypes.RegisterQueryServer(cfg.QueryServer(), am.controllerKeeper) + } + + if am.hostKeeper != nil { + hosttypes.RegisterQueryServer(cfg.QueryServer(), am.hostKeeper) + } } // InitGenesis performs genesis initialization for the interchain accounts module. From 5f25ecd0be468b622b36784a67d586272f9510e3 Mon Sep 17 00:00:00 2001 From: Lior Zilpa Date: Wed, 16 Nov 2022 14:40:51 +0200 Subject: [PATCH 104/140] Move client specific logic to SelfClient interface --- modules/core/02-client/keeper/keeper.go | 80 ++----------- modules/core/exported/client.go | 18 +++ modules/core/keeper/keeper.go | 13 +- modules/core/keeper/keeper_test.go | 2 + .../07-tendermint/types/self_client.go | 112 ++++++++++++++++++ testing/simapp/app.go | 3 +- 6 files changed, 153 insertions(+), 75 deletions(-) create mode 100644 modules/light-clients/07-tendermint/types/self_client.go diff --git a/modules/core/02-client/keeper/keeper.go b/modules/core/02-client/keeper/keeper.go index 74a043e9dea..cf558c174a7 100644 --- a/modules/core/02-client/keeper/keeper.go +++ b/modules/core/02-client/keeper/keeper.go @@ -2,7 +2,6 @@ package keeper import ( "fmt" - "reflect" "strings" "github.com/cosmos/cosmos-sdk/codec" @@ -12,13 +11,10 @@ import ( paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" "github.com/tendermint/tendermint/libs/log" - "github.com/tendermint/tendermint/light" "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" host "github.com/cosmos/ibc-go/v3/modules/core/24-host" "github.com/cosmos/ibc-go/v3/modules/core/exported" - ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" ) // Keeper represents a type that grants read and write permissions to any client @@ -29,10 +25,11 @@ type Keeper struct { paramSpace paramtypes.Subspace stakingKeeper types.StakingKeeper upgradeKeeper types.UpgradeKeeper + selfClient exported.SelfClient } // NewKeeper creates a new NewKeeper instance -func NewKeeper(cdc codec.BinaryCodec, key sdk.StoreKey, paramSpace paramtypes.Subspace, sk types.StakingKeeper, uk types.UpgradeKeeper) Keeper { +func NewKeeper(cdc codec.BinaryCodec, key sdk.StoreKey, paramSpace paramtypes.Subspace, sk types.StakingKeeper, uk types.UpgradeKeeper, selfClient exported.SelfClient) Keeper { // set KeyTable if it has not already been set if !paramSpace.HasKeyTable() { paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable()) @@ -44,6 +41,7 @@ func NewKeeper(cdc codec.BinaryCodec, key sdk.StoreKey, paramSpace paramtypes.Su paramSpace: paramSpace, stakingKeeper: sk, upgradeKeeper: uk, + selfClient: selfClient, } } @@ -256,77 +254,23 @@ func (k Keeper) GetSelfConsensusState(ctx sdk.Context, height exported.Height) ( return nil, sdkerrors.Wrapf(sdkerrors.ErrNotFound, "no historical info found at height %d", selfHeight.RevisionHeight) } - consensusState := &ibctmtypes.ConsensusState{ - Timestamp: histInfo.Header.Time, - Root: commitmenttypes.NewMerkleRoot(histInfo.Header.GetAppHash()), - NextValidatorsHash: histInfo.Header.NextValidatorsHash, + blockHeader, err := histInfo.Header.Marshal() + if err != nil { + return nil, err } - return consensusState, nil + + return k.selfClient.GetSelfConsensusStateFromBlocHeader(k.cdc, blockHeader) } // ValidateSelfClient validates the client parameters for a client of the running chain // This function is only used to validate the client state the counterparty stores for this chain // Client must be in same revision as the executing chain func (k Keeper) ValidateSelfClient(ctx sdk.Context, clientState exported.ClientState) error { - tmClient, ok := clientState.(*ibctmtypes.ClientState) - if !ok { - return sdkerrors.Wrapf(types.ErrInvalidClient, "client must be a Tendermint client, expected: %T, got: %T", - &ibctmtypes.ClientState{}, tmClient) - } - - if !tmClient.FrozenHeight.IsZero() { - return types.ErrClientFrozen - } - - if ctx.ChainID() != tmClient.ChainId { - return sdkerrors.Wrapf(types.ErrInvalidClient, "invalid chain-id. expected: %s, got: %s", - ctx.ChainID(), tmClient.ChainId) - } - - revision := types.ParseChainID(ctx.ChainID()) - - // client must be in the same revision as executing chain - if tmClient.LatestHeight.RevisionNumber != revision { - return sdkerrors.Wrapf(types.ErrInvalidClient, "client is not in the same revision as the chain. expected revision: %d, got: %d", - tmClient.LatestHeight.RevisionNumber, revision) - } - - selfHeight := types.NewHeight(revision, uint64(ctx.BlockHeight())) - if tmClient.LatestHeight.GTE(selfHeight) { - return sdkerrors.Wrapf(types.ErrInvalidClient, "client has LatestHeight %d greater than or equal to chain height %d", - tmClient.LatestHeight, selfHeight) - } - - expectedProofSpecs := commitmenttypes.GetSDKSpecs() - if !reflect.DeepEqual(expectedProofSpecs, tmClient.ProofSpecs) { - return sdkerrors.Wrapf(types.ErrInvalidClient, "client has invalid proof specs. expected: %v got: %v", - expectedProofSpecs, tmClient.ProofSpecs) - } - - if err := light.ValidateTrustLevel(tmClient.TrustLevel.ToTendermint()); err != nil { - return sdkerrors.Wrapf(types.ErrInvalidClient, "trust-level invalid: %v", err) - } - - expectedUbdPeriod := k.stakingKeeper.UnbondingTime(ctx) - if expectedUbdPeriod != tmClient.UnbondingPeriod { - return sdkerrors.Wrapf(types.ErrInvalidClient, "invalid unbonding period. expected: %s, got: %s", - expectedUbdPeriod, tmClient.UnbondingPeriod) - } - - if tmClient.UnbondingPeriod < tmClient.TrustingPeriod { - return sdkerrors.Wrapf(types.ErrInvalidClient, "unbonding period must be greater than trusting period. unbonding period (%d) < trusting period (%d)", - tmClient.UnbondingPeriod, tmClient.TrustingPeriod) - } - - if len(tmClient.UpgradePath) != 0 { - // For now, SDK IBC implementation assumes that upgrade path (if defined) is defined by SDK upgrade module - expectedUpgradePath := []string{upgradetypes.StoreKey, upgradetypes.KeyUpgradedIBCState} - if !reflect.DeepEqual(expectedUpgradePath, tmClient.UpgradePath) { - return sdkerrors.Wrapf(types.ErrInvalidClient, "upgrade path must be the upgrade path defined by upgrade module. expected %v, got %v", - expectedUpgradePath, tmClient.UpgradePath) - } + if k.selfClient.ClientType() != clientState.ClientType() { + return sdkerrors.Wrapf(types.ErrInvalidClient, "invalid client type. expected: %s, got: %s", + k.selfClient.ClientType(), clientState.ClientType()) } - return nil + return k.selfClient.ValidateSelfClientState(ctx, k.stakingKeeper.UnbondingTime(ctx), clientState) } // GetUpgradePlan executes the upgrade keeper GetUpgradePlan function. diff --git a/modules/core/exported/client.go b/modules/core/exported/client.go index 4dce203bea4..d11cf2002a6 100644 --- a/modules/core/exported/client.go +++ b/modules/core/exported/client.go @@ -1,6 +1,8 @@ package exported import ( + "time" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" proto "github.com/gogo/protobuf/proto" @@ -237,6 +239,22 @@ type GenesisMetadata interface { GetValue() []byte } +// SelfClient is an interface to create the chains' self client logic +type SelfClient interface { + ClientType() string + + ValidateSelfClientState( + ctx sdk.Context, + expectedUbdPeriod time.Duration, + clientState ClientState, + ) error + + GetSelfConsensusStateFromBlocHeader( + cdc codec.BinaryCodec, + blockHeader []byte, + ) (ConsensusState, error) +} + // String returns the string representation of a client status. func (s Status) String() string { return string(s) diff --git a/modules/core/keeper/keeper.go b/modules/core/keeper/keeper.go index 3a6cc5cb23b..d550197fff0 100644 --- a/modules/core/keeper/keeper.go +++ b/modules/core/keeper/keeper.go @@ -16,6 +16,7 @@ import ( channelkeeper "github.com/cosmos/ibc-go/v3/modules/core/04-channel/keeper" portkeeper "github.com/cosmos/ibc-go/v3/modules/core/05-port/keeper" porttypes "github.com/cosmos/ibc-go/v3/modules/core/05-port/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" "github.com/cosmos/ibc-go/v3/modules/core/types" ) @@ -39,7 +40,7 @@ type Keeper struct { func NewKeeper( cdc codec.BinaryCodec, key sdk.StoreKey, paramSpace paramtypes.Subspace, stakingKeeper clienttypes.StakingKeeper, upgradeKeeper clienttypes.UpgradeKeeper, - scopedKeeper capabilitykeeper.ScopedKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, selfClient exported.SelfClient, ) *Keeper { // register paramSpace at top level keeper // set KeyTable if it has not already been set @@ -52,17 +53,17 @@ func NewKeeper( // panic if any of the keepers passed in is empty if reflect.ValueOf(stakingKeeper).IsZero() { panic(fmt.Errorf("cannot initialize IBC keeper: empty staking keeper")) - } - + } + if reflect.ValueOf(upgradeKeeper).IsZero() { panic(fmt.Errorf("cannot initialize IBC keeper: empty upgrade keeper")) - } - + } + if reflect.DeepEqual(capabilitykeeper.ScopedKeeper{}, scopedKeeper) { panic(fmt.Errorf("cannot initialize IBC keeper: empty scoped keeper")) } - clientKeeper := clientkeeper.NewKeeper(cdc, key, paramSpace, stakingKeeper, upgradeKeeper) + clientKeeper := clientkeeper.NewKeeper(cdc, key, paramSpace, stakingKeeper, upgradeKeeper, selfClient) connectionKeeper := connectionkeeper.NewKeeper(cdc, key, paramSpace, clientKeeper) portKeeper := portkeeper.NewKeeper(scopedKeeper) channelKeeper := channelkeeper.NewKeeper(cdc, key, clientKeeper, connectionKeeper, portKeeper, scopedKeeper) diff --git a/modules/core/keeper/keeper_test.go b/modules/core/keeper/keeper_test.go index d40d1ab34c5..f0740740abd 100644 --- a/modules/core/keeper/keeper_test.go +++ b/modules/core/keeper/keeper_test.go @@ -14,6 +14,7 @@ import ( clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" ibchost "github.com/cosmos/ibc-go/v3/modules/core/24-host" ibckeeper "github.com/cosmos/ibc-go/v3/modules/core/keeper" + ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" ibctesting "github.com/cosmos/ibc-go/v3/testing" ) @@ -70,6 +71,7 @@ func (suite *KeeperTestSuite) TestNewKeeper() { stakingKeeper, upgradeKeeper, scopedKeeper, + ibctmtypes.NewSelfClient(), ) } ) diff --git a/modules/light-clients/07-tendermint/types/self_client.go b/modules/light-clients/07-tendermint/types/self_client.go new file mode 100644 index 00000000000..320eb1d84c7 --- /dev/null +++ b/modules/light-clients/07-tendermint/types/self_client.go @@ -0,0 +1,112 @@ +package types + +import ( + "reflect" + "time" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/tendermint/tendermint/light" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" +) + +var _ exported.SelfClient = (*SelfClient)(nil) + +type SelfClient struct{} + +// NewClientState creates a new ClientState instance +func NewSelfClient() exported.SelfClient { + return &SelfClient{} +} + +// ValidateSelfClientState validates the client parameters for a client of the running chain +// This function is only used to validate the client state the counterparty stores for this chain +// Client must be in same revision as the executing chain +func (sc SelfClient) ValidateSelfClientState( + ctx sdk.Context, + expectedUbdPeriod time.Duration, + clientState exported.ClientState, +) error { + tmClient, ok := clientState.(*ClientState) + if !ok { + return sdkerrors.Wrapf(clienttypes.ErrInvalidClient, "client must be a Tendermint client, expected: %T, got: %T", + &ClientState{}, tmClient) + } + + if !tmClient.FrozenHeight.IsZero() { + return clienttypes.ErrClientFrozen + } + + if ctx.ChainID() != tmClient.ChainId { + return sdkerrors.Wrapf(clienttypes.ErrInvalidClient, "invalid chain-id. expected: %s, got: %s", + ctx.ChainID(), tmClient.ChainId) + } + + revision := clienttypes.ParseChainID(ctx.ChainID()) + + // client must be in the same revision as executing chain + if tmClient.LatestHeight.RevisionNumber != revision { + return sdkerrors.Wrapf(clienttypes.ErrInvalidClient, "client is not in the same revision as the chain. expected revision: %d, got: %d", + tmClient.LatestHeight.RevisionNumber, revision) + } + + selfHeight := clienttypes.NewHeight(revision, uint64(ctx.BlockHeight())) + if tmClient.LatestHeight.GTE(selfHeight) { + return sdkerrors.Wrapf(clienttypes.ErrInvalidClient, "client has LatestHeight %d greater than or equal to chain height %d", + tmClient.LatestHeight, selfHeight) + } + + expectedProofSpecs := commitmenttypes.GetSDKSpecs() + if !reflect.DeepEqual(expectedProofSpecs, tmClient.ProofSpecs) { + return sdkerrors.Wrapf(clienttypes.ErrInvalidClient, "client has invalid proof specs. expected: %v got: %v", + expectedProofSpecs, tmClient.ProofSpecs) + } + + if err := light.ValidateTrustLevel(tmClient.TrustLevel.ToTendermint()); err != nil { + return sdkerrors.Wrapf(clienttypes.ErrInvalidClient, "trust-level invalid: %v", err) + } + + if expectedUbdPeriod != tmClient.UnbondingPeriod { + return sdkerrors.Wrapf(clienttypes.ErrInvalidClient, "invalid unbonding period. expected: %s, got: %s", + expectedUbdPeriod, tmClient.UnbondingPeriod) + } + + if tmClient.UnbondingPeriod < tmClient.TrustingPeriod { + return sdkerrors.Wrapf(clienttypes.ErrInvalidClient, "unbonding period must be greater than trusting period. unbonding period (%d) < trusting period (%d)", + tmClient.UnbondingPeriod, tmClient.TrustingPeriod) + } + + if len(tmClient.UpgradePath) != 0 { + // For now, SDK IBC implementation assumes that upgrade path (if defined) is defined by SDK upgrade module + expectedUpgradePath := []string{upgradetypes.StoreKey, upgradetypes.KeyUpgradedIBCState} + if !reflect.DeepEqual(expectedUpgradePath, tmClient.UpgradePath) { + return sdkerrors.Wrapf(clienttypes.ErrInvalidClient, "upgrade path must be the upgrade path defined by upgrade module. expected %v, got %v", + expectedUpgradePath, tmClient.UpgradePath) + } + } + return nil +} + +func (sc SelfClient) GetSelfConsensusStateFromBlocHeader( + cdc codec.BinaryCodec, + blockHeader []byte, +) (exported.ConsensusState, error) { + // unmarshal block header + tmBlockHeader := &tmproto.Header{} + if err := cdc.Unmarshal(blockHeader, tmBlockHeader); err != nil { + return nil, sdkerrors.Wrapf(clienttypes.ErrInvalidHeader, "could not unmarshal block header: %v", err) + } + return NewConsensusState(tmBlockHeader.Time, + commitmenttypes.NewMerkleRoot(tmBlockHeader.GetAppHash()), + tmBlockHeader.NextValidatorsHash), nil +} + +func (sc SelfClient) ClientType() string { + return exported.Tendermint +} diff --git a/testing/simapp/app.go b/testing/simapp/app.go index ee148806852..13f58ce61d4 100644 --- a/testing/simapp/app.go +++ b/testing/simapp/app.go @@ -99,6 +99,7 @@ import ( porttypes "github.com/cosmos/ibc-go/v3/modules/core/05-port/types" ibchost "github.com/cosmos/ibc-go/v3/modules/core/24-host" ibckeeper "github.com/cosmos/ibc-go/v3/modules/core/keeper" + ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" ibcmock "github.com/cosmos/ibc-go/v3/testing/mock" authz "github.com/cosmos/cosmos-sdk/x/authz" @@ -322,7 +323,7 @@ func NewSimApp( // Create IBC Keeper app.IBCKeeper = ibckeeper.NewKeeper( - appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, + appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, ibctmtypes.NewSelfClient(), ) app.AuthzKeeper = authzkeeper.NewKeeper(keys[authzkeeper.StoreKey], appCodec, app.BaseApp.MsgServiceRouter()) From 68a2df914ee1604cf57e5341af08b6b0dc4a50dc Mon Sep 17 00:00:00 2001 From: Lior Zilpa Date: Thu, 17 Nov 2022 17:56:35 +0200 Subject: [PATCH 105/140] Creating TestChainClientI and TestChainTendermint having the Tendermint logic from TestChain --- testing/app.go | 10 +- testing/chain.go | 226 ++++++-------------------------- testing/chain_tendermint.go | 254 ++++++++++++++++++++++++++++++++++++ testing/coordinator.go | 45 ++++++- testing/endpoint.go | 32 +---- testing/simapp/app.go | 24 +++- 6 files changed, 362 insertions(+), 229 deletions(-) create mode 100644 testing/chain_tendermint.go diff --git a/testing/app.go b/testing/app.go index 3c6a14ed453..b5cb38ccee3 100644 --- a/testing/app.go +++ b/testing/app.go @@ -27,7 +27,7 @@ import ( "github.com/cosmos/ibc-go/v3/testing/simapp" ) -var DefaultTestingAppInit func() (TestingApp, map[string]json.RawMessage) = SetupTestingApp +var DefaultTestingAppInit func(chainConsensusType string) (TestingApp, map[string]json.RawMessage) = SetupTestingApp type TestingApp interface { abci.Application @@ -47,10 +47,10 @@ type TestingApp interface { LastBlockHeight() int64 } -func SetupTestingApp() (TestingApp, map[string]json.RawMessage) { +func SetupTestingApp(chainConsensusType string) (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{}) + app := simapp.NewSimAppWithConsensusType(log.NewNopLogger(), db, nil, true, map[int64]bool{}, simapp.DefaultNodeHome, 5, chainConsensusType, encCdc, simapp.EmptyAppOptions{}) return app, simapp.NewDefaultGenesisState(encCdc.Marshaler) } @@ -58,8 +58,8 @@ func SetupTestingApp() (TestingApp, map[string]json.RawMessage) { // 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(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount, chainID string, powerReduction sdk.Int, balances ...banktypes.Balance) TestingApp { - app, genesisState := DefaultTestingAppInit() +func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount, chainID string, chainConsensusType string, powerReduction sdk.Int, balances ...banktypes.Balance) TestingApp { + app, genesisState := DefaultTestingAppInit(chainConsensusType) // set genesis accounts authGenesis := authtypes.NewGenesisState(authtypes.DefaultParams(), genAccs) diff --git a/testing/chain.go b/testing/chain.go index 28382a6c463..b28691ea708 100644 --- a/testing/chain.go +++ b/testing/chain.go @@ -1,7 +1,6 @@ package ibctesting import ( - "bytes" "fmt" "testing" "time" @@ -11,7 +10,6 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" @@ -20,18 +18,13 @@ import ( stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto/tmhash" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - tmprotoversion "github.com/tendermint/tendermint/proto/tendermint/version" tmtypes "github.com/tendermint/tendermint/types" - tmversion "github.com/tendermint/tendermint/version" clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" host "github.com/cosmos/ibc-go/v3/modules/core/24-host" "github.com/cosmos/ibc-go/v3/modules/core/exported" "github.com/cosmos/ibc-go/v3/modules/core/types" - ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" "github.com/cosmos/ibc-go/v3/testing/mock" "github.com/cosmos/ibc-go/v3/testing/simapp" ) @@ -43,6 +36,27 @@ type SenderAccount struct { SenderAccount authtypes.AccountI } +type TestChainClientI interface { + GetContext() sdk.Context + NextBlock() + BeginBlock() + UpdateCurrentHeaderTime(t time.Time) + ClientConfigToState(ClientConfig ClientConfig) exported.ClientState + GetConsensusState() exported.ConsensusState +} + +func NewTestChainClient(chain *TestChain, chainConsensusType string) TestChainClientI { + // set the last header to the current header + // use nil trusted fields + switch chainConsensusType { + case exported.Tendermint: + return NewChainTendermintClient(chain) + default: + panic(fmt.Sprintf("client type %s is not supported", chainConsensusType)) + } + +} + // 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 @@ -51,14 +65,12 @@ type SenderAccount struct { type TestChain struct { *testing.T - Coordinator *Coordinator - App TestingApp - ChainID string - LastHeader *ibctmtypes.Header // header for last block height committed - CurrentHeader tmproto.Header // header for current block height - QueryServer types.QueryServer - TxConfig client.TxConfig - Codec codec.BinaryCodec + Coordinator *Coordinator + App TestingApp + ChainID string + QueryServer types.QueryServer + TxConfig client.TxConfig + Codec codec.BinaryCodec Vals *tmtypes.ValidatorSet Signers []tmtypes.PrivValidator @@ -68,6 +80,8 @@ type TestChain struct { SenderAccount authtypes.AccountI SenderAccounts []SenderAccount + + TestChainClient TestChainClientI } // NewTestChainWithValSet initializes a new TestChain instance with the given validator set @@ -85,7 +99,7 @@ type TestChain struct { // // CONTRACT: Validator and signer array must be provided in the order expected by Tendermint. // i.e. sorted first by power and then lexicographically by address. -func NewTestChainWithValSet(t *testing.T, coord *Coordinator, chainID string, valSet *tmtypes.ValidatorSet, signers []tmtypes.PrivValidator) *TestChain { +func NewTestChainWithValSet(t *testing.T, coord *Coordinator, chainID string, valSet *tmtypes.ValidatorSet, signers []tmtypes.PrivValidator, chainConsensusType string) *TestChain { genAccs := []authtypes.GenesisAccount{} genBals := []banktypes.Balance{} senderAccs := []SenderAccount{} @@ -113,14 +127,7 @@ func NewTestChainWithValSet(t *testing.T, coord *Coordinator, chainID string, va senderAccs = append(senderAccs, senderAcc) } - app := SetupWithGenesisValSet(t, valSet, genAccs, chainID, sdk.DefaultPowerReduction, genBals...) - - // create current header and call begin block - header := tmproto.Header{ - ChainID: chainID, - Height: 1, - Time: coord.CurrentTime.UTC(), - } + app := SetupWithGenesisValSet(t, valSet, genAccs, chainID, chainConsensusType, sdk.DefaultPowerReduction, genBals...) txConfig := app.GetTxConfig() @@ -130,7 +137,6 @@ func NewTestChainWithValSet(t *testing.T, coord *Coordinator, chainID string, va Coordinator: coord, ChainID: chainID, App: app, - CurrentHeader: header, QueryServer: app.GetIBCKeeper(), TxConfig: txConfig, Codec: app.AppCodec(), @@ -141,6 +147,9 @@ func NewTestChainWithValSet(t *testing.T, coord *Coordinator, chainID string, va SenderAccounts: senderAccs, } + // build consensus spesific client + chain.TestChainClient = NewTestChainClient(chain, chainConsensusType) + coord.CommitBlock(chain) return chain @@ -148,7 +157,7 @@ func NewTestChainWithValSet(t *testing.T, coord *Coordinator, chainID string, va // 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 { +func NewTestChain(t *testing.T, coord *Coordinator, chainID string, chainConsensusType string) *TestChain { // generate validators private/public key var ( validatorsPerChain = 4 @@ -175,12 +184,12 @@ func NewTestChain(t *testing.T, coord *Coordinator, chainID string) *TestChain { signers = append(signers, signersByAddress[val.PubKey.Address().String()]) } - return NewTestChainWithValSet(t, coord, chainID, valSet, signers) + return NewTestChainWithValSet(t, coord, chainID, valSet, signers, chainConsensusType) } // GetContext returns the current context for the application. func (chain *TestChain) GetContext() sdk.Context { - return chain.App.GetBaseApp().NewContext(false, chain.CurrentHeader) + return chain.TestChainClient.GetContext() } // GetSimApp returns the SimApp to allow usage ofnon-interface fields. @@ -264,23 +273,7 @@ func (chain *TestChain) QueryConsensusStateProof(clientID string) ([]byte, clien // // CONTRACT: this function must only be called after app.Commit() occurs func (chain *TestChain) NextBlock() { - // set the last header to the current header - // use nil trusted fields - chain.LastHeader = chain.CurrentTMClientHeader() - - // increment the current header - chain.CurrentHeader = tmproto.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.CurrentHeader.Time, - ValidatorsHash: chain.Vals.Hash(), - NextValidatorsHash: chain.Vals.Hash(), - } - - chain.App.BeginBlock(abci.RequestBeginBlock{Header: chain.CurrentHeader}) + chain.TestChainClient.NextBlock() } // sendMsgs delivers a transaction through the application without returning the result. @@ -368,155 +361,12 @@ func (chain *TestChain) GetPrefix() commitmenttypes.MerklePrefix { return commitmenttypes.NewMerklePrefix(chain.App.GetIBCKeeper().ConnectionKeeper.GetCommitmentPrefix().Bytes()) } -// ConstructUpdateTMClientHeader will construct a valid 07-tendermint Header to update the -// light client on the source chain. -func (chain *TestChain) ConstructUpdateTMClientHeader(counterparty *TestChain, clientID string) (*ibctmtypes.Header, error) { - return chain.ConstructUpdateTMClientHeaderWithTrustedHeight(counterparty, clientID, clienttypes.ZeroHeight()) -} - -// ConstructUpdateTMClientHeader will construct a valid 07-tendermint Header to update the -// light client on the source chain. -func (chain *TestChain) ConstructUpdateTMClientHeaderWithTrustedHeight(counterparty *TestChain, clientID string, trustedHeight clienttypes.Height) (*ibctmtypes.Header, error) { - header := counterparty.LastHeader - // Relayer must query for LatestHeight on client to get TrustedHeight if the trusted height is not set - if trustedHeight.IsZero() { - trustedHeight = chain.GetClientState(clientID).GetLatestHeight().(clienttypes.Height) - } - var ( - tmTrustedVals *tmtypes.ValidatorSet - ok bool - ) - // Once we get TrustedHeight from client, we must query the validators from the counterparty chain - // If the LatestHeight == LastHeader.Height, then TrustedValidators are current validators - // If LatestHeight < LastHeader.Height, we can query the historical validator set from HistoricalInfo - if trustedHeight == counterparty.LastHeader.GetHeight() { - tmTrustedVals = counterparty.Vals - } else { - // NOTE: We need to get validators from counterparty at height: trustedHeight+1 - // since the last trusted validators for a header at height h - // is the NextValidators at h+1 committed to in header h by - // NextValidatorsHash - tmTrustedVals, ok = counterparty.GetValsAtHeight(int64(trustedHeight.RevisionHeight + 1)) - if !ok { - return nil, sdkerrors.Wrapf(ibctmtypes.ErrInvalidHeaderHeight, "could not retrieve trusted validators at trustedHeight: %d", trustedHeight) - } - } - // inject trusted fields into last header - // for now assume revision number is 0 - header.TrustedHeight = trustedHeight - - trustedVals, err := tmTrustedVals.ToProto() - if err != nil { - return nil, err - } - header.TrustedValidators = trustedVals - - return header, nil -} - // 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() *ibctmtypes.Header { - return chain.CreateTMClientHeader(chain.ChainID, chain.CurrentHeader.Height, clienttypes.Height{}, chain.CurrentHeader.Time, chain.Vals, nil, chain.Signers) -} - -// 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, tmValSet, tmTrustedVals *tmtypes.ValidatorSet, signers []tmtypes.PrivValidator) *ibctmtypes.Header { - var ( - valSet *tmproto.ValidatorSet - trustedVals *tmproto.ValidatorSet - ) - require.NotNil(chain.T, tmValSet) - - vsetHash := tmValSet.Hash() - - tmHeader := tmtypes.Header{ - Version: tmprotoversion.Consensus{Block: tmversion.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: tmhash.Sum([]byte("data_hash")), - ValidatorsHash: vsetHash, - NextValidatorsHash: vsetHash, - ConsensusHash: tmhash.Sum([]byte("consensus_hash")), - AppHash: chain.CurrentHeader.AppHash, - LastResultsHash: tmhash.Sum([]byte("last_results_hash")), - EvidenceHash: tmhash.Sum([]byte("evidence_hash")), - ProposerAddress: tmValSet.Proposer.Address, //nolint:staticcheck - } - - hhash := tmHeader.Hash() - blockID := MakeBlockID(hhash, 3, tmhash.Sum([]byte("part_set"))) - voteSet := tmtypes.NewVoteSet(chainID, blockHeight, 1, tmproto.PrecommitType, tmValSet) - - commit, err := tmtypes.MakeCommit(blockID, blockHeight, 1, voteSet, signers, timestamp) - require.NoError(chain.T, err) - - signedHeader := &tmproto.SignedHeader{ - Header: tmHeader.ToProto(), - Commit: commit.ToProto(), - } - - if tmValSet != nil { - valSet, err = tmValSet.ToProto() - require.NoError(chain.T, err) - } - - if tmTrustedVals != nil { - trustedVals, err = tmTrustedVals.ToProto() - require.NoError(chain.T, err) - } - - // 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 &ibctmtypes.Header{ - SignedHeader: signedHeader, - ValidatorSet: valSet, - TrustedHeight: trustedHeight, - TrustedValidators: trustedVals, - } -} - -// MakeBlockID copied unimported test functions from tmtypes to use them here -func MakeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) tmtypes.BlockID { - return tmtypes.BlockID{ - Hash: hash, - PartSetHeader: tmtypes.PartSetHeader{ - Total: partSetSize, - Hash: partSetHash, - }, - } -} - -// CreateSortedSignerArray takes two PrivValidators, and the corresponding Validator structs -// (including voting power). It returns a signer array of PrivValidators that matches the -// sorting of ValidatorSet. -// The sorting is first by .VotingPower (descending), with secondary index of .Address (ascending). -func CreateSortedSignerArray(altPrivVal, suitePrivVal tmtypes.PrivValidator, - altVal, suiteVal *tmtypes.Validator, -) []tmtypes.PrivValidator { - switch { - case altVal.VotingPower > suiteVal.VotingPower: - return []tmtypes.PrivValidator{altPrivVal, suitePrivVal} - case altVal.VotingPower < suiteVal.VotingPower: - return []tmtypes.PrivValidator{suitePrivVal, altPrivVal} - default: - if bytes.Compare(altVal.Address, suiteVal.Address) == -1 { - return []tmtypes.PrivValidator{altPrivVal, suitePrivVal} - } - return []tmtypes.PrivValidator{suitePrivVal, altPrivVal} - } -} - // CreatePortCapability binds and claims a capability for the given portID if it does not // already exist. This function will fail testing on any resulting error. // NOTE: only creation of a capability for a transfer or mock port is supported diff --git a/testing/chain_tendermint.go b/testing/chain_tendermint.go new file mode 100644 index 00000000000..1baa523c3fa --- /dev/null +++ b/testing/chain_tendermint.go @@ -0,0 +1,254 @@ +package ibctesting + +import ( + "bytes" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/tmhash" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + tmprotoversion "github.com/tendermint/tendermint/proto/tendermint/version" + tmtypes "github.com/tendermint/tendermint/types" + tmversion "github.com/tendermint/tendermint/version" + + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" + ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" +) + +// TestChainTendermint is a testing struct that 'wraps' a TestChain with the last TM Header, +// the current ABCI header. +type TestChainTendermint struct { + TC *TestChain + + LastHeader *ibctmtypes.Header // header for last block height committed + CurrentHeader tmproto.Header // header for current block height + +} + +var _ TestChainClientI = &TestChainTendermint{} + +// NewChainTendermintClient initializes the consunsus spesisifc pare of the TestChain +func NewChainTendermintClient(tc *TestChain) *TestChainTendermint { + + // create current header and call begin block + header := tmproto.Header{ + ChainID: tc.ChainID, + Height: 1, + Time: tc.Coordinator.CurrentTime.UTC(), + } + + // create an account to send transactions from + chain := &TestChainTendermint{ + tc, + nil, + header, + } + + return chain +} + +// GetContext returns the current context for the application. +func (chain *TestChainTendermint) GetContext() sdk.Context { + return chain.TC.App.GetBaseApp().NewContext(false, chain.CurrentHeader) +} + +// 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. +// +// CONTRACT: this function must only be called after app.Commit() occurs +func (chain *TestChainTendermint) NextBlock() { + // set the last header to the current header + // use nil trusted fields + chain.LastHeader = chain.CurrentTMClientHeader() + + // increment the current header + chain.CurrentHeader = tmproto.Header{ + ChainID: chain.TC.ChainID, + Height: chain.TC.App.LastBlockHeight() + 1, + AppHash: chain.TC.App.LastCommitID().Hash, + // NOTE: the time is increased by the coordinator to maintain time synchrony amongst + // chains. + Time: chain.CurrentHeader.Time, + ValidatorsHash: chain.TC.Vals.Hash(), + NextValidatorsHash: chain.TC.Vals.Hash(), + } + + chain.BeginBlock() +} + +// ConstructUpdateTMClientHeader will construct a valid 07-tendermint Header to update the +// light client on the source chain. +func (chain *TestChainTendermint) ConstructUpdateTMClientHeader(counterparty *TestChain, clientID string) (*ibctmtypes.Header, error) { + return chain.ConstructUpdateTMClientHeaderWithTrustedHeight(counterparty, clientID, clienttypes.ZeroHeight()) +} + +// ConstructUpdateTMClientHeader will construct a valid 07-tendermint Header to update the +// light client on the source chain. +func (chain *TestChainTendermint) ConstructUpdateTMClientHeaderWithTrustedHeight(counterparty *TestChain, clientID string, trustedHeight clienttypes.Height) (*ibctmtypes.Header, error) { + counterpartyTestChainTendermint := counterparty.TestChainClient.(*TestChainTendermint) + header := counterpartyTestChainTendermint.LastHeader + // Relayer must query for LatestHeight on client to get TrustedHeight if the trusted height is not set + if trustedHeight.IsZero() { + trustedHeight = chain.TC.GetClientState(clientID).GetLatestHeight().(clienttypes.Height) + } + var ( + tmTrustedVals *tmtypes.ValidatorSet + ok bool + ) + // Once we get TrustedHeight from client, we must query the validators from the counterparty chain + // If the LatestHeight == LastHeader.Height, then TrustedValidators are current validators + // If LatestHeight < LastHeader.Height, we can query the historical validator set from HistoricalInfo + if trustedHeight == counterpartyTestChainTendermint.LastHeader.GetHeight() { + tmTrustedVals = counterparty.Vals + } else { + // NOTE: We need to get validators from counterparty at height: trustedHeight+1 + // since the last trusted validators for a header at height h + // is the NextValidators at h+1 committed to in header h by + // NextValidatorsHash + tmTrustedVals, ok = counterparty.GetValsAtHeight(int64(trustedHeight.RevisionHeight + 1)) + if !ok { + return nil, sdkerrors.Wrapf(ibctmtypes.ErrInvalidHeaderHeight, "could not retrieve trusted validators at trustedHeight: %d", trustedHeight) + } + } + // inject trusted fields into last header + // for now assume revision number is 0 + header.TrustedHeight = trustedHeight + + trustedVals, err := tmTrustedVals.ToProto() + if err != nil { + return nil, err + } + header.TrustedValidators = trustedVals + + return header, nil +} + +// 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 *TestChainTendermint) CurrentTMClientHeader() *ibctmtypes.Header { + return chain.CreateTMClientHeader(chain.TC.ChainID, chain.CurrentHeader.Height, clienttypes.Height{}, chain.CurrentHeader.Time, chain.TC.Vals, nil, chain.TC.Signers) +} + +// 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 *TestChainTendermint) CreateTMClientHeader(chainID string, blockHeight int64, trustedHeight clienttypes.Height, timestamp time.Time, tmValSet, tmTrustedVals *tmtypes.ValidatorSet, signers []tmtypes.PrivValidator) *ibctmtypes.Header { + var ( + valSet *tmproto.ValidatorSet + trustedVals *tmproto.ValidatorSet + ) + require.NotNil(chain.TC.T, tmValSet) + + vsetHash := tmValSet.Hash() + + tmHeader := tmtypes.Header{ + Version: tmprotoversion.Consensus{Block: tmversion.BlockProtocol, App: 2}, + ChainID: chainID, + Height: blockHeight, + Time: timestamp, + LastBlockID: MakeBlockID(make([]byte, tmhash.Size), 10_000, make([]byte, tmhash.Size)), + LastCommitHash: chain.TC.App.LastCommitID().Hash, + DataHash: tmhash.Sum([]byte("data_hash")), + ValidatorsHash: vsetHash, + NextValidatorsHash: vsetHash, + ConsensusHash: tmhash.Sum([]byte("consensus_hash")), + AppHash: chain.CurrentHeader.AppHash, + LastResultsHash: tmhash.Sum([]byte("last_results_hash")), + EvidenceHash: tmhash.Sum([]byte("evidence_hash")), + ProposerAddress: tmValSet.Proposer.Address, //nolint:staticcheck + } + + hhash := tmHeader.Hash() + blockID := MakeBlockID(hhash, 3, tmhash.Sum([]byte("part_set"))) + voteSet := tmtypes.NewVoteSet(chainID, blockHeight, 1, tmproto.PrecommitType, tmValSet) + + commit, err := tmtypes.MakeCommit(blockID, blockHeight, 1, voteSet, signers, timestamp) + require.NoError(chain.TC.T, err) + + signedHeader := &tmproto.SignedHeader{ + Header: tmHeader.ToProto(), + Commit: commit.ToProto(), + } + + if tmValSet != nil { + valSet, err = tmValSet.ToProto() + require.NoError(chain.TC.T, err) + } + + if tmTrustedVals != nil { + trustedVals, err = tmTrustedVals.ToProto() + require.NoError(chain.TC.T, err) + } + + // 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 &ibctmtypes.Header{ + SignedHeader: signedHeader, + ValidatorSet: valSet, + TrustedHeight: trustedHeight, + TrustedValidators: trustedVals, + } +} + +// MakeBlockID copied unimported test functions from tmtypes to use them here +func MakeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) tmtypes.BlockID { + return tmtypes.BlockID{ + Hash: hash, + PartSetHeader: tmtypes.PartSetHeader{ + Total: partSetSize, + Hash: partSetHash, + }, + } +} + +// CreateSortedSignerArray takes two PrivValidators, and the corresponding Validator structs +// (including voting power). It returns a signer array of PrivValidators that matches the +// sorting of ValidatorSet. +// The sorting is first by .VotingPower (descending), with secondary index of .Address (ascending). +func CreateSortedSignerArray(altPrivVal, suitePrivVal tmtypes.PrivValidator, + altVal, suiteVal *tmtypes.Validator, +) []tmtypes.PrivValidator { + switch { + case altVal.VotingPower > suiteVal.VotingPower: + return []tmtypes.PrivValidator{altPrivVal, suitePrivVal} + case altVal.VotingPower < suiteVal.VotingPower: + return []tmtypes.PrivValidator{suitePrivVal, altPrivVal} + default: + if bytes.Compare(altVal.Address, suiteVal.Address) == -1 { + return []tmtypes.PrivValidator{altPrivVal, suitePrivVal} + } + return []tmtypes.PrivValidator{suitePrivVal, altPrivVal} + } +} + +// UpdateTimeForChain updates the clock for this chain. +func (chain *TestChainTendermint) UpdateCurrentHeaderTime(t time.Time) { + chain.CurrentHeader.Time = t +} + +// BeginBlock signals the beginning of a block with chain.CurrentHeader +func (chain *TestChainTendermint) BeginBlock() { + chain.TC.App.BeginBlock(abci.RequestBeginBlock{Header: chain.CurrentHeader}) +} + +// ClientConfigToState builds the ClientState based on the clientConfig and last header +func (chain *TestChainTendermint) ClientConfigToState(clientConfig ClientConfig) exported.ClientState { + tmConfig, ok := clientConfig.(*TendermintConfig) + require.True(chain.TC.T, ok) + + height := chain.LastHeader.GetHeight().(clienttypes.Height) + clientState := ibctmtypes.NewClientState( + chain.TC.ChainID, tmConfig.TrustLevel, tmConfig.TrustingPeriod, tmConfig.UnbondingPeriod, tmConfig.MaxClockDrift, + height, commitmenttypes.GetSDKSpecs(), UpgradePath, tmConfig.AllowUpdateAfterExpiry, tmConfig.AllowUpdateAfterMisbehaviour, + ) + return clientState +} + +// GetConsensusState returns the consensus state of the last header +func (chain *TestChainTendermint) GetConsensusState() exported.ConsensusState { + return chain.LastHeader.ConsensusState() +} diff --git a/testing/coordinator.go b/testing/coordinator.go index 217c257473a..fa3b9bbc22d 100644 --- a/testing/coordinator.go +++ b/testing/coordinator.go @@ -6,8 +6,9 @@ import ( "testing" "time" + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" "github.com/stretchr/testify/require" - abci "github.com/tendermint/tendermint/abci/types" ) var ( @@ -25,17 +26,47 @@ type Coordinator struct { Chains map[string]*TestChain } +// UniqueStringLists returns a list without dupicates +func StringSliceRemoveDuplicates(stringSlice []string) []string { + keys := make(map[string]bool) + list := []string{} + for _, entry := range stringSlice { + if _, value := keys[entry]; !value { + keys[entry] = true + list = append(list, entry) + } + } + return list +} + // NewCoordinator initializes Coordinator with N TestChain's func NewCoordinator(t *testing.T, n int) *Coordinator { + consensusTypes := make([]string, n) + for i := 0; i < len(consensusTypes); i++ { + consensusTypes[i] = exported.Tendermint + } + + return NewCoordinatorWithConsensusType(t, consensusTypes) +} + +// NewCoordinatorWithConsensusType initializes Coordinator with len(consensusTypes) TestChain's +// where the self client type of chain i is consensusTypes[i] +func NewCoordinatorWithConsensusType(t *testing.T, consensusTypes []string) *Coordinator { chains := make(map[string]*TestChain) coord := &Coordinator{ T: t, CurrentTime: globalStartTime, } - for i := 1; i <= n; i++ { - chainID := GetChainID(i) - chains[chainID] = NewTestChain(t, coord, chainID) + for i, consensusType := range consensusTypes { + chainID := GetChainID(i + 1) + chains[chainID] = NewTestChain(t, coord, chainID, consensusType) + // add the consensusTypes to AllowedClients list + clientKeeper := chains[chainID].App.GetIBCKeeper().ClientKeeper + params := clientKeeper.GetParams(chains[chainID].GetContext()) + clientKeeper.SetParams(chains[chainID].GetContext(), clienttypes.NewParams( + StringSliceRemoveDuplicates(append(params.AllowedClients, consensusTypes...))..., + )) } coord.Chains = chains @@ -66,8 +97,8 @@ func (coord *Coordinator) UpdateTime() { // UpdateTimeForChain updates the clock for a specific chain. func (coord *Coordinator) UpdateTimeForChain(chain *TestChain) { - chain.CurrentHeader.Time = coord.CurrentTime.UTC() - chain.App.BeginBlock(abci.RequestBeginBlock{Header: chain.CurrentHeader}) + chain.TestChainClient.UpdateCurrentHeaderTime(coord.CurrentTime.UTC()) + chain.TestChainClient.BeginBlock() } // Setup constructs a TM client, connection, and channel on both chains provided. It will @@ -190,7 +221,7 @@ func (coord *Coordinator) CommitBlock(chains ...*TestChain) { // 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.App.BeginBlock(abci.RequestBeginBlock{Header: chain.CurrentHeader}) + chain.TestChainClient.BeginBlock() chain.App.Commit() chain.NextBlock() coord.IncrementTime() diff --git a/testing/endpoint.go b/testing/endpoint.go index 607f7a16843..fa13ec97063 100644 --- a/testing/endpoint.go +++ b/testing/endpoint.go @@ -9,10 +9,8 @@ import ( clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" connectiontypes "github.com/cosmos/ibc-go/v3/modules/core/03-connection/types" channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" - commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" host "github.com/cosmos/ibc-go/v3/modules/core/24-host" "github.com/cosmos/ibc-go/v3/modules/core/exported" - ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" ) // Endpoint is a which represents a channel endpoint and its associated @@ -80,31 +78,8 @@ func (endpoint *Endpoint) CreateClient() (err error) { // ensure counterparty has committed state endpoint.Chain.Coordinator.CommitBlock(endpoint.Counterparty.Chain) - var ( - clientState exported.ClientState - consensusState exported.ConsensusState - ) - - switch endpoint.ClientConfig.GetClientType() { - case exported.Tendermint: - tmConfig, ok := endpoint.ClientConfig.(*TendermintConfig) - require.True(endpoint.Chain.T, ok) - - height := endpoint.Counterparty.Chain.LastHeader.GetHeight().(clienttypes.Height) - clientState = ibctmtypes.NewClientState( - endpoint.Counterparty.Chain.ChainID, tmConfig.TrustLevel, tmConfig.TrustingPeriod, tmConfig.UnbondingPeriod, tmConfig.MaxClockDrift, - height, commitmenttypes.GetSDKSpecs(), UpgradePath, tmConfig.AllowUpdateAfterExpiry, tmConfig.AllowUpdateAfterMisbehaviour, - ) - consensusState = endpoint.Counterparty.Chain.LastHeader.ConsensusState() - case exported.Solomachine: - // TODO - // solo := NewSolomachine(endpoint.Chain.T, endpoint.Chain.Codec, clientID, "", 1) - // clientState = solo.ClientState() - // consensusState = solo.ConsensusState() - - default: - err = fmt.Errorf("client type %s is not supported", endpoint.ClientConfig.GetClientType()) - } + clientState := endpoint.Counterparty.Chain.TestChainClient.ClientConfigToState(endpoint.ClientConfig) + consensusState := endpoint.Counterparty.Chain.TestChainClient.GetConsensusState() if err != nil { return err @@ -135,7 +110,8 @@ func (endpoint *Endpoint) UpdateClient() (err error) { switch endpoint.ClientConfig.GetClientType() { case exported.Tendermint: - header, err = endpoint.Chain.ConstructUpdateTMClientHeader(endpoint.Counterparty.Chain, endpoint.ClientID) + testChainTendermint := endpoint.Chain.TestChainClient.(*TestChainTendermint) + header, err = testChainTendermint.ConstructUpdateTMClientHeader(endpoint.Counterparty.Chain, endpoint.ClientID) default: err = fmt.Errorf("client type %s is not supported", endpoint.ClientConfig.GetClientType()) diff --git a/testing/simapp/app.go b/testing/simapp/app.go index 13f58ce61d4..bbfbbe7690f 100644 --- a/testing/simapp/app.go +++ b/testing/simapp/app.go @@ -2,6 +2,7 @@ package simapp import ( "encoding/json" + "fmt" "io" "net/http" "os" @@ -106,6 +107,8 @@ import ( authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" authzmodule "github.com/cosmos/cosmos-sdk/x/authz/module" + "github.com/cosmos/ibc-go/v3/modules/core/exported" + // unnamed import of statik for swagger UI support _ "github.com/cosmos/cosmos-sdk/client/docs/statik" ) @@ -231,11 +234,23 @@ func init() { } // NewSimApp returns a reference to an initialized SimApp. +// Assume self chain client type is Tendermint func NewSimApp( logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, skipUpgradeHeights map[int64]bool, homePath string, invCheckPeriod uint, encodingConfig simappparams.EncodingConfig, appOpts servertypes.AppOptions, baseAppOptions ...func(*baseapp.BaseApp), ) *SimApp { + return NewSimAppWithConsensusType(logger, db, traceStore, loadLatest, skipUpgradeHeights, + homePath, invCheckPeriod, exported.Tendermint, encodingConfig, + appOpts, baseAppOptions...) +} + +// NewSimApp returns a reference to an initialized SimApp. +func NewSimAppWithConsensusType( + logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, skipUpgradeHeights map[int64]bool, + homePath string, invCheckPeriod uint, chainConsensusType string, encodingConfig simappparams.EncodingConfig, + appOpts servertypes.AppOptions, baseAppOptions ...func(*baseapp.BaseApp), +) *SimApp { appCodec := encodingConfig.Marshaler legacyAmino := encodingConfig.Amino @@ -321,9 +336,16 @@ func NewSimApp( stakingtypes.NewMultiStakingHooks(app.DistrKeeper.Hooks(), app.SlashingKeeper.Hooks()), ) + var selfClient exported.SelfClient + switch chainConsensusType { + case exported.Tendermint: + selfClient = ibctmtypes.NewSelfClient() + default: + panic(fmt.Sprintf("client type %s is not supported", chainConsensusType)) + } // Create IBC Keeper app.IBCKeeper = ibckeeper.NewKeeper( - appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, ibctmtypes.NewSelfClient(), + appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, selfClient, ) app.AuthzKeeper = authzkeeper.NewKeeper(keys[authzkeeper.StoreKey], appCodec, app.BaseApp.MsgServiceRouter()) From ea1aea0f7b9b48c6722692754474e2af34471b4d Mon Sep 17 00:00:00 2001 From: Lior Zilpa Date: Thu, 17 Nov 2022 17:59:49 +0200 Subject: [PATCH 106/140] Fixing all the places where tendermint logic was used from TestChain --- modules/core/02-client/keeper/client_test.go | 50 ++++++----- modules/core/02-client/keeper/keeper_test.go | 6 +- modules/core/02-client/types/codec_test.go | 6 +- modules/core/02-client/types/genesis_test.go | 4 +- modules/core/02-client/types/msgs_test.go | 42 +++++---- .../core/03-connection/keeper/keeper_test.go | 4 +- modules/core/ante/ante_test.go | 7 +- modules/core/genesis_test.go | 6 +- .../07-tendermint/types/header_test.go | 11 +-- .../types/misbehaviour_handle_test.go | 90 ++++++++++--------- .../07-tendermint/types/misbehaviour_test.go | 32 ++++--- .../07-tendermint/types/store_test.go | 4 +- .../07-tendermint/types/tendermint_test.go | 3 +- .../07-tendermint/types/update_test.go | 44 ++++----- 14 files changed, 171 insertions(+), 138 deletions(-) diff --git a/modules/core/02-client/keeper/client_test.go b/modules/core/02-client/keeper/client_test.go index 4ca764620bf..dd37764f08b 100644 --- a/modules/core/02-client/keeper/client_test.go +++ b/modules/core/02-client/keeper/client_test.go @@ -49,15 +49,17 @@ func (suite *KeeperTestSuite) TestUpdateClientTendermint() { // Must create header creation functions since suite.header gets recreated on each test case createFutureUpdateFn := func(trustedHeight clienttypes.Height) *ibctmtypes.Header { - header, err := suite.chainA.ConstructUpdateTMClientHeaderWithTrustedHeight(path.EndpointB.Chain, path.EndpointA.ClientID, trustedHeight) + chainATendermint := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint) + header, err := chainATendermint.ConstructUpdateTMClientHeaderWithTrustedHeight(path.EndpointB.Chain, path.EndpointA.ClientID, trustedHeight) suite.Require().NoError(err) return header } createPastUpdateFn := func(fillHeight, trustedHeight clienttypes.Height) *ibctmtypes.Header { + chainBTendermint := suite.chainB.TestChainClient.(*ibctesting.TestChainTendermint) 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.(*ibctmtypes.ConsensusState).Timestamp.Add(time.Second*5), + return chainBTendermint.CreateTMClientHeader(suite.chainB.ChainID, int64(fillHeight.RevisionHeight), trustedHeight, consState.(*ibctmtypes.ConsensusState).Timestamp.Add(time.Second*5), suite.chainB.Vals, suite.chainB.Vals, suite.chainB.Signers) } @@ -80,7 +82,8 @@ func (suite *KeeperTestSuite) TestUpdateClientTendermint() { clientState := path.EndpointA.GetClientState() trustedHeight := clientState.GetLatestHeight().(types.Height) - currHeight := suite.chainB.CurrentHeader.Height + chainBTendermint := suite.chainB.TestChainClient.(*ibctesting.TestChainTendermint) + currHeight := chainBTendermint.CurrentHeader.Height fillHeight := types.NewHeight(clientState.GetLatestHeight().GetRevisionNumber(), uint64(currHeight)) // commit a couple blocks to allow client to fill in gaps @@ -432,6 +435,8 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { suite.Require().NoError(err) altVal := tmtypes.NewValidator(altPubKey, 4) + chainATendermint := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint) + // Set valSet here with suite.valSet so it doesn't get reset on each testcase valSet := suite.valSet valsHash := valSet.Hash() @@ -463,8 +468,8 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { { "trusting period misbehavior should pass", &ibctmtypes.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight+1), testClientHeight, altTime, bothValSet, bothValSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight+1), testClientHeight, suite.ctx.BlockTime(), bothValSet, bothValSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight+1), testClientHeight, altTime, bothValSet, bothValSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight+1), testClientHeight, suite.ctx.BlockTime(), bothValSet, bothValSet, bothSigners), ClientId: clientID, }, func() error { @@ -479,8 +484,8 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { { "time misbehavior should pass", &ibctmtypes.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight+5), testClientHeight, suite.ctx.BlockTime(), bothValSet, bothValSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight+1), testClientHeight, altTime, bothValSet, bothValSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight+5), testClientHeight, suite.ctx.BlockTime(), bothValSet, bothValSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight+1), testClientHeight, altTime, bothValSet, bothValSet, bothSigners), ClientId: clientID, }, func() error { @@ -495,8 +500,8 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { { "misbehavior at later height should pass", &ibctmtypes.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(testChainID, int64(heightPlus5.RevisionHeight+1), testClientHeight, altTime, bothValSet, valSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(testChainID, int64(heightPlus5.RevisionHeight+1), testClientHeight, suite.ctx.BlockTime(), bothValSet, valSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(testChainID, int64(heightPlus5.RevisionHeight+1), testClientHeight, altTime, bothValSet, valSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(testChainID, int64(heightPlus5.RevisionHeight+1), testClientHeight, suite.ctx.BlockTime(), bothValSet, valSet, bothSigners), ClientId: clientID, }, func() error { @@ -521,8 +526,8 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { { "misbehavior at later height with different trusted heights should pass", &ibctmtypes.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(testChainID, int64(heightPlus5.RevisionHeight+1), testClientHeight, altTime, bothValSet, valSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(testChainID, int64(heightPlus5.RevisionHeight+1), heightPlus3, suite.ctx.BlockTime(), bothValSet, bothValSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(testChainID, int64(heightPlus5.RevisionHeight+1), testClientHeight, altTime, bothValSet, valSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(testChainID, int64(heightPlus5.RevisionHeight+1), heightPlus3, suite.ctx.BlockTime(), bothValSet, bothValSet, bothSigners), ClientId: clientID, }, func() error { @@ -547,8 +552,8 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { { "misbehavior ValidateBasic fails: misbehaviour height is at same height as trusted height", &ibctmtypes.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight), testClientHeight, altTime, bothValSet, bothValSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight), testClientHeight, suite.ctx.BlockTime(), bothValSet, bothValSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight), testClientHeight, altTime, bothValSet, bothValSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight), testClientHeight, suite.ctx.BlockTime(), bothValSet, bothValSet, bothSigners), ClientId: clientID, }, func() error { @@ -563,8 +568,8 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { { "trusted ConsensusState1 not found", &ibctmtypes.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(testChainID, int64(heightPlus5.RevisionHeight+1), heightPlus3, altTime, bothValSet, bothValSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(testChainID, int64(heightPlus5.RevisionHeight+1), testClientHeight, suite.ctx.BlockTime(), bothValSet, valSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(testChainID, int64(heightPlus5.RevisionHeight+1), heightPlus3, altTime, bothValSet, bothValSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(testChainID, int64(heightPlus5.RevisionHeight+1), testClientHeight, suite.ctx.BlockTime(), bothValSet, valSet, bothSigners), ClientId: clientID, }, func() error { @@ -579,8 +584,8 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { { "trusted ConsensusState2 not found", &ibctmtypes.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(testChainID, int64(heightPlus5.RevisionHeight+1), testClientHeight, altTime, bothValSet, valSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(testChainID, int64(heightPlus5.RevisionHeight+1), heightPlus3, suite.ctx.BlockTime(), bothValSet, bothValSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(testChainID, int64(heightPlus5.RevisionHeight+1), testClientHeight, altTime, bothValSet, valSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(testChainID, int64(heightPlus5.RevisionHeight+1), heightPlus3, suite.ctx.BlockTime(), bothValSet, bothValSet, bothSigners), ClientId: clientID, }, func() error { @@ -601,8 +606,8 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { { "client already is not active - client is frozen", &ibctmtypes.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight+1), testClientHeight, altTime, bothValSet, bothValSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight+1), testClientHeight, suite.ctx.BlockTime(), bothValSet, bothValSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight+1), testClientHeight, altTime, bothValSet, bothValSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight+1), testClientHeight, suite.ctx.BlockTime(), bothValSet, bothValSet, bothSigners), ClientId: clientID, }, func() error { @@ -620,8 +625,8 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { { "misbehaviour check failed", &ibctmtypes.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight+1), testClientHeight, altTime, bothValSet, bothValSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight+1), testClientHeight, suite.ctx.BlockTime(), altValSet, bothValSet, altSigners), + Header1: chainATendermint.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight+1), testClientHeight, altTime, bothValSet, bothValSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight+1), testClientHeight, suite.ctx.BlockTime(), altValSet, bothValSet, altSigners), ClientId: clientID, }, func() error { @@ -668,7 +673,8 @@ func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { func (suite *KeeperTestSuite) TestUpdateClientEventEmission() { path := ibctesting.NewPath(suite.chainA, suite.chainB) suite.coordinator.SetupClients(path) - header, err := suite.chainA.ConstructUpdateTMClientHeader(suite.chainB, path.EndpointA.ClientID) + testChainTendermint := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint) + header, err := testChainTendermint.ConstructUpdateTMClientHeader(suite.chainB, path.EndpointA.ClientID) suite.Require().NoError(err) msg, err := clienttypes.NewMsgUpdateClient( diff --git a/modules/core/02-client/keeper/keeper_test.go b/modules/core/02-client/keeper/keeper_test.go index e3dfb4d2e3b..44c5e037cf0 100644 --- a/modules/core/02-client/keeper/keeper_test.go +++ b/modules/core/02-client/keeper/keeper_test.go @@ -95,7 +95,8 @@ func (suite *KeeperTestSuite) SetupTest() { validator := tmtypes.NewValidator(pubKey, 1) suite.valSet = tmtypes.NewValidatorSet([]*tmtypes.Validator{validator}) suite.valSetHash = suite.valSet.Hash() - suite.header = suite.chainA.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight), testClientHeightMinus1, now2, suite.valSet, suite.valSet, []tmtypes.PrivValidator{suite.privVal}) + chainATendermint := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint) + suite.header = chainATendermint.CreateTMClientHeader(testChainID, int64(testClientHeight.RevisionHeight), testClientHeightMinus1, now2, suite.valSet, suite.valSet, []tmtypes.PrivValidator{suite.privVal}) suite.consensusState = ibctmtypes.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot([]byte("hash")), suite.valSetHash) var validators stakingtypes.Validators @@ -328,7 +329,8 @@ func (suite KeeperTestSuite) TestConsensusStateHelpers() { testClientHeightPlus5 := types.NewHeight(0, height+5) - header := suite.chainA.CreateTMClientHeader(testClientID, int64(testClientHeightPlus5.RevisionHeight), testClientHeight, suite.header.Header.Time.Add(time.Minute), + chainATendermint := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint) + header := chainATendermint.CreateTMClientHeader(testClientID, int64(testClientHeightPlus5.RevisionHeight), testClientHeight, suite.header.Header.Time.Add(time.Minute), suite.valSet, suite.valSet, []tmtypes.PrivValidator{suite.privVal}) // mock update functionality diff --git a/modules/core/02-client/types/codec_test.go b/modules/core/02-client/types/codec_test.go index 85b53e5ad84..2e4364a37a7 100644 --- a/modules/core/02-client/types/codec_test.go +++ b/modules/core/02-client/types/codec_test.go @@ -83,7 +83,7 @@ func (suite *TypesTestSuite) TestPackConsensusState() { }, { "tendermint consensus", - suite.chainA.LastHeader.ConsensusState(), + suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint).LastHeader.ConsensusState(), true, }, { @@ -129,7 +129,7 @@ func (suite *TypesTestSuite) TestPackHeader() { }, { "tendermint header", - suite.chainA.LastHeader, + suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint).LastHeader, true, }, { @@ -176,7 +176,7 @@ func (suite *TypesTestSuite) TestPackMisbehaviour() { }, { "tendermint misbehaviour", - ibctmtypes.NewMisbehaviour("tendermint", suite.chainA.LastHeader, suite.chainA.LastHeader), + ibctmtypes.NewMisbehaviour("tendermint", suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint).LastHeader, suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint).LastHeader), true, }, { diff --git a/modules/core/02-client/types/genesis_test.go b/modules/core/02-client/types/genesis_test.go index 6972a8d5983..151a1c9eabd 100644 --- a/modules/core/02-client/types/genesis_test.go +++ b/modules/core/02-client/types/genesis_test.go @@ -56,7 +56,9 @@ func (suite *TypesTestSuite) TestValidateGenesis() { valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{val}) heightMinus1 := types.NewHeight(0, height-1) - header := suite.chainA.CreateTMClientHeader(chainID, int64(clientHeight.RevisionHeight), heightMinus1, now, valSet, valSet, []tmtypes.PrivValidator{privVal}) + chainATendermint := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint) + + header := chainATendermint.CreateTMClientHeader(chainID, int64(clientHeight.RevisionHeight), heightMinus1, now, valSet, valSet, []tmtypes.PrivValidator{privVal}) testCases := []struct { name string diff --git a/modules/core/02-client/types/msgs_test.go b/modules/core/02-client/types/msgs_test.go index 35dd08aedba..466a14cfb8d 100644 --- a/modules/core/02-client/types/msgs_test.go +++ b/modules/core/02-client/types/msgs_test.go @@ -55,8 +55,10 @@ func (suite *TypesTestSuite) TestMarshalMsgCreateClient() { }, { "tendermint client", func() { + chainATendermint := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint) + tendermintClient := ibctmtypes.NewClientState(suite.chainA.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath, false, false) - msg, err = types.NewMsgCreateClient(tendermintClient, suite.chainA.CurrentTMClientHeader().ConsensusState(), suite.chainA.SenderAccount.GetAddress().String()) + msg, err = types.NewMsgCreateClient(tendermintClient, chainATendermint.CurrentTMClientHeader().ConsensusState(), suite.chainA.SenderAccount.GetAddress().String()) suite.Require().NoError(err) }, }, @@ -92,6 +94,8 @@ func (suite *TypesTestSuite) TestMsgCreateClient_ValidateBasic() { err error ) + chainATendermint := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint) + cases := []struct { name string malleate func() @@ -101,7 +105,7 @@ func (suite *TypesTestSuite) TestMsgCreateClient_ValidateBasic() { "valid - tendermint client", func() { tendermintClient := ibctmtypes.NewClientState(suite.chainA.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath, false, false) - msg, err = types.NewMsgCreateClient(tendermintClient, suite.chainA.CurrentTMClientHeader().ConsensusState(), suite.chainA.SenderAccount.GetAddress().String()) + msg, err = types.NewMsgCreateClient(tendermintClient, chainATendermint.CurrentTMClientHeader().ConsensusState(), suite.chainA.SenderAccount.GetAddress().String()) suite.Require().NoError(err) }, true, @@ -109,7 +113,7 @@ func (suite *TypesTestSuite) TestMsgCreateClient_ValidateBasic() { { "invalid tendermint client", func() { - msg, err = types.NewMsgCreateClient(&ibctmtypes.ClientState{}, suite.chainA.CurrentTMClientHeader().ConsensusState(), suite.chainA.SenderAccount.GetAddress().String()) + msg, err = types.NewMsgCreateClient(&ibctmtypes.ClientState{}, chainATendermint.CurrentTMClientHeader().ConsensusState(), suite.chainA.SenderAccount.GetAddress().String()) suite.Require().NoError(err) }, false, @@ -125,7 +129,7 @@ func (suite *TypesTestSuite) TestMsgCreateClient_ValidateBasic() { "failed to unpack consensus state", func() { tendermintClient := ibctmtypes.NewClientState(suite.chainA.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath, false, false) - msg, err = types.NewMsgCreateClient(tendermintClient, suite.chainA.CurrentTMClientHeader().ConsensusState(), suite.chainA.SenderAccount.GetAddress().String()) + msg, err = types.NewMsgCreateClient(tendermintClient, chainATendermint.CurrentTMClientHeader().ConsensusState(), suite.chainA.SenderAccount.GetAddress().String()) suite.Require().NoError(err) msg.ConsensusState = nil }, @@ -195,7 +199,6 @@ func (suite *TypesTestSuite) TestMarshalMsgUpdateClient() { msg *types.MsgUpdateClient err error ) - testCases := []struct { name string malleate func() @@ -209,7 +212,8 @@ func (suite *TypesTestSuite) TestMarshalMsgUpdateClient() { }, { "tendermint client", func() { - msg, err = types.NewMsgUpdateClient("tendermint", suite.chainA.CurrentTMClientHeader(), suite.chainA.SenderAccount.GetAddress().String()) + chainATendermint := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint) + msg, err = types.NewMsgUpdateClient("tendermint", chainATendermint.CurrentTMClientHeader(), suite.chainA.SenderAccount.GetAddress().String()) suite.Require().NoError(err) }, @@ -261,7 +265,8 @@ func (suite *TypesTestSuite) TestMsgUpdateClient_ValidateBasic() { { "valid - tendermint header", func() { - msg, err = types.NewMsgUpdateClient("tendermint", suite.chainA.CurrentTMClientHeader(), suite.chainA.SenderAccount.GetAddress().String()) + chainATendermint := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint) + msg, err = types.NewMsgUpdateClient("tendermint", chainATendermint.CurrentTMClientHeader(), suite.chainA.SenderAccount.GetAddress().String()) suite.Require().NoError(err) }, true, @@ -308,7 +313,8 @@ func (suite *TypesTestSuite) TestMsgUpdateClient_ValidateBasic() { { "unsupported - localhost", func() { - msg, err = types.NewMsgUpdateClient(exported.Localhost, suite.chainA.CurrentTMClientHeader(), suite.chainA.SenderAccount.GetAddress().String()) + chainATendermint := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint) + msg, err = types.NewMsgUpdateClient(exported.Localhost, chainATendermint.CurrentTMClientHeader(), suite.chainA.SenderAccount.GetAddress().String()) suite.Require().NoError(err) }, false, @@ -488,10 +494,12 @@ func (suite *TypesTestSuite) TestMarshalMsgSubmitMisbehaviour() { }, { "tendermint client", func() { - height := types.NewHeight(0, uint64(suite.chainA.CurrentHeader.Height)) - heightMinus1 := types.NewHeight(0, uint64(suite.chainA.CurrentHeader.Height)-1) - header1 := suite.chainA.CreateTMClientHeader(suite.chainA.ChainID, int64(height.RevisionHeight), heightMinus1, suite.chainA.CurrentHeader.Time, suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Signers) - header2 := suite.chainA.CreateTMClientHeader(suite.chainA.ChainID, int64(height.RevisionHeight), heightMinus1, suite.chainA.CurrentHeader.Time.Add(time.Minute), suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Signers) + chainATendermint := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint) + + height := types.NewHeight(0, uint64(chainATendermint.CurrentHeader.Height)) + heightMinus1 := types.NewHeight(0, uint64(chainATendermint.CurrentHeader.Height)-1) + header1 := chainATendermint.CreateTMClientHeader(suite.chainA.ChainID, int64(height.RevisionHeight), heightMinus1, chainATendermint.CurrentHeader.Time, suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Signers) + header2 := chainATendermint.CreateTMClientHeader(suite.chainA.ChainID, int64(height.RevisionHeight), heightMinus1, chainATendermint.CurrentHeader.Time.Add(time.Minute), suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Signers) misbehaviour := ibctmtypes.NewMisbehaviour("tendermint", header1, header2) msg, err = types.NewMsgSubmitMisbehaviour("tendermint", misbehaviour, suite.chainA.SenderAccount.GetAddress().String()) @@ -546,10 +554,12 @@ func (suite *TypesTestSuite) TestMsgSubmitMisbehaviour_ValidateBasic() { { "valid - tendermint misbehaviour", func() { - height := types.NewHeight(0, uint64(suite.chainA.CurrentHeader.Height)) - heightMinus1 := types.NewHeight(0, uint64(suite.chainA.CurrentHeader.Height)-1) - header1 := suite.chainA.CreateTMClientHeader(suite.chainA.ChainID, int64(height.RevisionHeight), heightMinus1, suite.chainA.CurrentHeader.Time, suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Signers) - header2 := suite.chainA.CreateTMClientHeader(suite.chainA.ChainID, int64(height.RevisionHeight), heightMinus1, suite.chainA.CurrentHeader.Time.Add(time.Minute), suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Signers) + chainATendermint := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint) + + height := types.NewHeight(0, uint64(chainATendermint.CurrentHeader.Height)) + heightMinus1 := types.NewHeight(0, uint64(chainATendermint.CurrentHeader.Height)-1) + header1 := chainATendermint.CreateTMClientHeader(suite.chainA.ChainID, int64(height.RevisionHeight), heightMinus1, chainATendermint.CurrentHeader.Time, suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Signers) + header2 := chainATendermint.CreateTMClientHeader(suite.chainA.ChainID, int64(height.RevisionHeight), heightMinus1, chainATendermint.CurrentHeader.Time.Add(time.Minute), suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Signers) misbehaviour := ibctmtypes.NewMisbehaviour("tendermint", header1, header2) msg, err = types.NewMsgSubmitMisbehaviour("tendermint", misbehaviour, suite.chainA.SenderAccount.GetAddress().String()) diff --git a/modules/core/03-connection/keeper/keeper_test.go b/modules/core/03-connection/keeper/keeper_test.go index b4dfa839028..88610201946 100644 --- a/modules/core/03-connection/keeper/keeper_test.go +++ b/modules/core/03-connection/keeper/keeper_test.go @@ -137,12 +137,12 @@ func (suite *KeeperTestSuite) TestGetTimestampAtHeight() { tc.malleate() actualTimestamp, err := suite.chainA.App.GetIBCKeeper().ConnectionKeeper.GetTimestampAtHeight( - suite.chainA.GetContext(), connection, suite.chainB.LastHeader.GetHeight(), + suite.chainA.GetContext(), connection, suite.chainB.TestChainClient.(*ibctesting.TestChainTendermint).LastHeader.GetHeight(), ) if tc.expPass { suite.Require().NoError(err) - suite.Require().EqualValues(uint64(suite.chainB.LastHeader.GetTime().UnixNano()), actualTimestamp) + suite.Require().EqualValues(uint64(suite.chainB.TestChainClient.(*ibctesting.TestChainTendermint).LastHeader.GetTime().UnixNano()), actualTimestamp) } else { suite.Require().Error(err) } diff --git a/modules/core/ante/ante_test.go b/modules/core/ante/ante_test.go index c04f6483f74..b36dde94400 100644 --- a/modules/core/ante/ante_test.go +++ b/modules/core/ante/ante_test.go @@ -93,7 +93,7 @@ func (suite *AnteTestSuite) createAcknowledgementMessage(sequenceNumber uint64, // createTimeoutMessage creates an Timeout message for a packet sent from chain B to chain A. func (suite *AnteTestSuite) createTimeoutMessage(sequenceNumber uint64, isRedundant bool) sdk.Msg { - height := suite.chainA.LastHeader.GetHeight() + height := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint).LastHeader.GetHeight() timeoutHeight := clienttypes.NewHeight(height.GetRevisionNumber(), height.GetRevisionHeight()+1) packet := channeltypes.NewPacket(ibctesting.MockPacketData, sequenceNumber, suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, @@ -121,7 +121,7 @@ func (suite *AnteTestSuite) createTimeoutMessage(sequenceNumber uint64, isRedund // createTimeoutOnCloseMessage creates an TimeoutOnClose message for a packet sent from chain B to chain A. func (suite *AnteTestSuite) createTimeoutOnCloseMessage(sequenceNumber uint64, isRedundant bool) sdk.Msg { - height := suite.chainA.LastHeader.GetHeight() + height := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint).LastHeader.GetHeight() timeoutHeight := clienttypes.NewHeight(height.GetRevisionNumber(), height.GetRevisionHeight()+1) packet := channeltypes.NewPacket(ibctesting.MockPacketData, sequenceNumber, suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, @@ -157,7 +157,8 @@ func (suite *AnteTestSuite) createUpdateClientMessage() sdk.Msg { switch endpoint.ClientConfig.GetClientType() { case exported.Tendermint: - header, _ = endpoint.Chain.ConstructUpdateTMClientHeader(endpoint.Counterparty.Chain, endpoint.ClientID) + testChainTendermint := endpoint.Chain.TestChainClient.(*ibctesting.TestChainTendermint) + header, _ = testChainTendermint.ConstructUpdateTMClientHeader(endpoint.Counterparty.Chain, endpoint.ClientID) default: } diff --git a/modules/core/genesis_test.go b/modules/core/genesis_test.go index 00b9a0c3b84..ce159de72e7 100644 --- a/modules/core/genesis_test.go +++ b/modules/core/genesis_test.go @@ -59,7 +59,8 @@ func TestIBCTestSuite(t *testing.T) { } func (suite *IBCTestSuite) TestValidateGenesis() { - header := suite.chainA.CreateTMClientHeader(suite.chainA.ChainID, suite.chainA.CurrentHeader.Height, clienttypes.NewHeight(0, uint64(suite.chainA.CurrentHeader.Height-1)), suite.chainA.CurrentHeader.Time, suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Signers) + chainATendermint := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint) + header := chainATendermint.CreateTMClientHeader(suite.chainA.ChainID, chainATendermint.CurrentHeader.Height, clienttypes.NewHeight(0, uint64(chainATendermint.CurrentHeader.Height-1)), chainATendermint.CurrentHeader.Time, suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Signers) testCases := []struct { name string @@ -225,7 +226,8 @@ func (suite *IBCTestSuite) TestValidateGenesis() { } func (suite *IBCTestSuite) TestInitGenesis() { - header := suite.chainA.CreateTMClientHeader(suite.chainA.ChainID, suite.chainA.CurrentHeader.Height, clienttypes.NewHeight(0, uint64(suite.chainA.CurrentHeader.Height-1)), suite.chainA.CurrentHeader.Time, suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Signers) + chainATendermint := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint) + header := chainATendermint.CreateTMClientHeader(suite.chainA.ChainID, chainATendermint.CurrentHeader.Height, clienttypes.NewHeight(0, uint64(chainATendermint.CurrentHeader.Height-1)), chainATendermint.CurrentHeader.Time, suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Signers) testCases := []struct { name string diff --git a/modules/light-clients/07-tendermint/types/header_test.go b/modules/light-clients/07-tendermint/types/header_test.go index f57776c092f..65a122109b3 100644 --- a/modules/light-clients/07-tendermint/types/header_test.go +++ b/modules/light-clients/07-tendermint/types/header_test.go @@ -8,15 +8,16 @@ import ( clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" "github.com/cosmos/ibc-go/v3/modules/core/exported" "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" + ibctesting "github.com/cosmos/ibc-go/v3/testing" ) func (suite *TendermintTestSuite) TestGetHeight() { - header := suite.chainA.LastHeader + header := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint).LastHeader suite.Require().NotEqual(uint64(0), header.GetHeight()) } func (suite *TendermintTestSuite) TestGetTime() { - header := suite.chainA.LastHeader + header := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint).LastHeader suite.Require().NotEqual(time.Time{}, header.GetTime()) } @@ -40,7 +41,7 @@ func (suite *TendermintTestSuite) TestHeaderValidateBasic() { header.SignedHeader.Commit.Height = -1 }, false}, {"signed header failed tendermint ValidateBasic", func() { - header = suite.chainA.LastHeader + header = suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint).LastHeader header.SignedHeader.Commit = nil }, false}, {"trusted height is equal to header height", func() { @@ -54,7 +55,7 @@ func (suite *TendermintTestSuite) TestHeaderValidateBasic() { }, false}, {"header validator hash does not equal hash of validator set", func() { // use chainB's randomly generated validator set - header.ValidatorSet = suite.chainB.LastHeader.ValidatorSet + header.ValidatorSet = suite.chainB.TestChainClient.(*ibctesting.TestChainTendermint).LastHeader.ValidatorSet }, false}, } @@ -66,7 +67,7 @@ func (suite *TendermintTestSuite) TestHeaderValidateBasic() { suite.Run(tc.name, func() { suite.SetupTest() - header = suite.chainA.LastHeader // must be explicitly changed in malleate + header = suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint).LastHeader // must be explicitly changed in malleate tc.malleate() diff --git a/modules/light-clients/07-tendermint/types/misbehaviour_handle_test.go b/modules/light-clients/07-tendermint/types/misbehaviour_handle_test.go index da1efc665da..1a4b063e721 100644 --- a/modules/light-clients/07-tendermint/types/misbehaviour_handle_test.go +++ b/modules/light-clients/07-tendermint/types/misbehaviour_handle_test.go @@ -38,6 +38,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { heightMinus1 := clienttypes.NewHeight(height.RevisionNumber, height.RevisionHeight-1) heightMinus3 := clienttypes.NewHeight(height.RevisionNumber, height.RevisionHeight-3) + chainATendermint := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint) + testCases := []struct { name string clientState exported.ClientState @@ -57,8 +59,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ClientId: chainID, }, suite.now, @@ -72,8 +74,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+3), height, suite.now, bothValSet, bothValSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+3), height, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), ClientId: chainID, }, suite.now, @@ -87,8 +89,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+3), height, suite.now, bothValSet, bothValSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Hour), bothValSet, bothValSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+3), height, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Hour), bothValSet, bothValSet, bothSigners), ClientId: chainID, }, suite.now, @@ -102,8 +104,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), heightMinus1, &types.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ClientId: chainID, }, suite.now, @@ -117,8 +119,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), heightMinus3, &types.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus3, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus3, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), ClientId: chainID, }, suite.now, @@ -132,8 +134,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), heightMinus3, &types.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(chainIDRevision0, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(chainIDRevision0, int64(height.RevisionHeight+1), heightMinus3, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(chainIDRevision0, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(chainIDRevision0, int64(height.RevisionHeight+1), heightMinus3, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), ClientId: chainID, }, suite.now, @@ -147,8 +149,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), heightMinus3, &types.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(chainIDRevision0, 3, heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(chainIDRevision0, 3, heightMinus3, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(chainIDRevision0, 3, heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(chainIDRevision0, 3, heightMinus3, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), ClientId: chainID, }, suite.now, @@ -162,8 +164,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), heightMinus3, &types.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(chainIDRevision1, 1, heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(chainIDRevision1, 1, heightMinus3, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(chainIDRevision1, 1, heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(chainIDRevision1, 1, heightMinus3, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), ClientId: chainID, }, suite.now, @@ -177,8 +179,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), height, &types.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, suite.valSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, suite.valSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), ClientId: chainID, }, suite.now, @@ -192,8 +194,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), ClientId: chainID, }, suite.now, @@ -207,8 +209,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+3), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+3), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), ClientId: chainID, }, suite.now, @@ -222,8 +224,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader("ethermint", int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader("ethermint", int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader("ethermint", int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader("ethermint", int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ClientId: chainID, }, suite.now, @@ -237,8 +239,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), heightMinus3, &types.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), ClientId: chainID, }, suite.now, @@ -252,8 +254,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), heightMinus3, &types.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus3, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus3, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ClientId: chainID, }, suite.now, @@ -267,8 +269,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ClientId: chainID, }, suite.now, @@ -282,8 +284,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ClientId: chainID, }, suite.now, @@ -308,8 +310,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ClientId: chainID, }, suite.now, @@ -323,8 +325,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ClientId: chainID, }, suite.now.Add(trustingPeriod), @@ -338,8 +340,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, suite.valSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, suite.valSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), ClientId: chainID, }, suite.now, @@ -353,8 +355,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, altValSet, bothValSet, altSigners), - Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, altValSet, bothValSet, altSigners), + Header2: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ClientId: chainID, }, suite.now, @@ -368,8 +370,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), - Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), altValSet, bothValSet, altSigners), + Header1: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), altValSet, bothValSet, altSigners), ClientId: chainID, }, suite.now, @@ -383,8 +385,8 @@ func (suite *TendermintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, altValSet, bothValSet, altSigners), - Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), altValSet, bothValSet, altSigners), + Header1: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, altValSet, bothValSet, altSigners), + Header2: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), altValSet, bothValSet, altSigners), ClientId: chainID, }, suite.now, diff --git a/modules/light-clients/07-tendermint/types/misbehaviour_test.go b/modules/light-clients/07-tendermint/types/misbehaviour_test.go index bba616bc5e4..7cef1eee10f 100644 --- a/modules/light-clients/07-tendermint/types/misbehaviour_test.go +++ b/modules/light-clients/07-tendermint/types/misbehaviour_test.go @@ -18,9 +18,11 @@ func (suite *TendermintTestSuite) TestMisbehaviour() { signers := []tmtypes.PrivValidator{suite.privVal} heightMinus1 := clienttypes.NewHeight(0, height.RevisionHeight-1) + chainATendermint := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint) + misbehaviour := &types.Misbehaviour{ Header1: suite.header, - Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, signers), + Header2: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, signers), ClientId: clientID, } @@ -52,6 +54,8 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { heightMinus1 := clienttypes.NewHeight(0, height.RevisionHeight-1) + chainATendermint := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint) + testCases := []struct { name string misbehaviour *types.Misbehaviour @@ -62,7 +66,7 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { "valid fork misbehaviour, two headers at same height have different time", &types.Misbehaviour{ Header1: suite.header, - Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers), + Header2: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers), ClientId: clientID, }, func(misbehaviour *types.Misbehaviour) error { return nil }, @@ -71,7 +75,7 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { { "valid time misbehaviour, both headers at different heights are at same time", &types.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+5), heightMinus1, suite.now, suite.valSet, suite.valSet, signers), + Header1: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight+5), heightMinus1, suite.now, suite.valSet, suite.valSet, signers), Header2: suite.header, ClientId: clientID, }, @@ -94,7 +98,7 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { "valid misbehaviour with different trusted headers", &types.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, bothValSet, signers), + Header2: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight), clienttypes.NewHeight(0, height.RevisionHeight-3), suite.now.Add(time.Minute), suite.valSet, bothValSet, signers), ClientId: clientID, }, func(misbehaviour *types.Misbehaviour) error { return nil }, @@ -103,7 +107,7 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { { "trusted height is 0 in Header1", &types.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight), clienttypes.ZeroHeight(), suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers), + Header1: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight), clienttypes.ZeroHeight(), suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers), Header2: suite.header, ClientId: clientID, }, @@ -114,7 +118,7 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { "trusted height is 0 in Header2", &types.Misbehaviour{ Header1: suite.header, - Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight), clienttypes.ZeroHeight(), suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers), + Header2: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight), clienttypes.ZeroHeight(), suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers), ClientId: clientID, }, func(misbehaviour *types.Misbehaviour) error { return nil }, @@ -123,7 +127,7 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { { "trusted valset is nil in Header1", &types.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now.Add(time.Minute), suite.valSet, nil, signers), + Header1: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now.Add(time.Minute), suite.valSet, nil, signers), Header2: suite.header, ClientId: clientID, }, @@ -134,7 +138,7 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { "trusted valset is nil in Header2", &types.Misbehaviour{ Header1: suite.header, - Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now.Add(time.Minute), suite.valSet, nil, signers), + Header2: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now.Add(time.Minute), suite.valSet, nil, signers), ClientId: clientID, }, func(misbehaviour *types.Misbehaviour) error { return nil }, @@ -144,7 +148,7 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { "invalid client ID ", &types.Misbehaviour{ Header1: suite.header, - Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, signers), + Header2: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, signers), ClientId: "GAIA", }, func(misbehaviour *types.Misbehaviour) error { return nil }, @@ -154,7 +158,7 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { "chainIDs do not match", &types.Misbehaviour{ Header1: suite.header, - Header2: suite.chainA.CreateTMClientHeader("ethermint", int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, signers), + Header2: chainATendermint.CreateTMClientHeader("ethermint", int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, signers), ClientId: clientID, }, func(misbehaviour *types.Misbehaviour) error { return nil }, @@ -164,7 +168,7 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { "header2 height is greater", &types.Misbehaviour{ Header1: suite.header, - Header2: suite.chainA.CreateTMClientHeader(chainID, 6, clienttypes.NewHeight(0, height.RevisionHeight+1), suite.now, suite.valSet, suite.valSet, signers), + Header2: chainATendermint.CreateTMClientHeader(chainID, 6, clienttypes.NewHeight(0, height.RevisionHeight+1), suite.now, suite.valSet, suite.valSet, signers), ClientId: clientID, }, func(misbehaviour *types.Misbehaviour) error { return nil }, @@ -173,7 +177,7 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { { "header 1 doesn't have 2/3 majority", &types.Misbehaviour{ - Header1: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, bothValSet, suite.valSet, bothSigners), + Header1: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, bothValSet, suite.valSet, bothSigners), Header2: suite.header, ClientId: clientID, }, @@ -195,7 +199,7 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { "header 2 doesn't have 2/3 majority", &types.Misbehaviour{ Header1: suite.header, - Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, bothValSet, suite.valSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, bothValSet, suite.valSet, bothSigners), ClientId: clientID, }, func(misbehaviour *types.Misbehaviour) error { @@ -216,7 +220,7 @@ func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { "validators sign off on wrong commit", &types.Misbehaviour{ Header1: suite.header, - Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, bothValSet, suite.valSet, bothSigners), + Header2: chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, bothValSet, suite.valSet, bothSigners), ClientId: clientID, }, func(misbehaviour *types.Misbehaviour) error { diff --git a/modules/light-clients/07-tendermint/types/store_test.go b/modules/light-clients/07-tendermint/types/store_test.go index 22a8d069794..53de402ddb3 100644 --- a/modules/light-clients/07-tendermint/types/store_test.go +++ b/modules/light-clients/07-tendermint/types/store_test.go @@ -86,7 +86,7 @@ func (suite *TendermintTestSuite) TestGetProcessedTime() { suite.coordinator.UpdateTime() // coordinator increments time before creating client - expectedTime := suite.chainA.CurrentHeader.Time.Add(ibctesting.TimeIncrement) + expectedTime := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint).CurrentHeader.Time.Add(ibctesting.TimeIncrement) // Verify ProcessedTime on CreateClient err := path.EndpointA.CreateClient() @@ -102,7 +102,7 @@ func (suite *TendermintTestSuite) TestGetProcessedTime() { suite.coordinator.UpdateTime() // coordinator increments time before updating client - expectedTime = suite.chainA.CurrentHeader.Time.Add(ibctesting.TimeIncrement) + expectedTime = suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint).CurrentHeader.Time.Add(ibctesting.TimeIncrement) // Verify ProcessedTime on UpdateClient err = path.EndpointA.UpdateClient() diff --git a/modules/light-clients/07-tendermint/types/tendermint_test.go b/modules/light-clients/07-tendermint/types/tendermint_test.go index 7c1996abfa8..c644adcb71c 100644 --- a/modules/light-clients/07-tendermint/types/tendermint_test.go +++ b/modules/light-clients/07-tendermint/types/tendermint_test.go @@ -86,7 +86,8 @@ func (suite *TendermintTestSuite) SetupTest() { val := tmtypes.NewValidator(pubKey, 10) suite.valSet = tmtypes.NewValidatorSet([]*tmtypes.Validator{val}) suite.valsHash = suite.valSet.Hash() - suite.header = suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, []tmtypes.PrivValidator{suite.privVal}) + chainATendermint := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint) + suite.header = chainATendermint.CreateTMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, []tmtypes.PrivValidator{suite.privVal}) suite.ctx = app.BaseApp.NewContext(checkTx, tmproto.Header{Height: 1, Time: suite.now}) } diff --git a/modules/light-clients/07-tendermint/types/update_test.go b/modules/light-clients/07-tendermint/types/update_test.go index 77c993dff72..3406f50cc49 100644 --- a/modules/light-clients/07-tendermint/types/update_test.go +++ b/modules/light-clients/07-tendermint/types/update_test.go @@ -44,6 +44,8 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { altValSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{altVal}) altSigners := []tmtypes.PrivValidator{altPrivVal} + chainATendermint := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint) + testCases := []struct { name string setup func(*TendermintTestSuite) @@ -55,7 +57,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func(suite *TendermintTestSuite) { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) + newHeader = chainATendermint.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) currentTime = suite.now }, expFrozen: false, @@ -66,7 +68,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func(suite *TendermintTestSuite) { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus5.RevisionHeight), height, suite.headerTime, bothValSet, suite.valSet, bothSigners) + newHeader = chainATendermint.CreateTMClientHeader(chainID, int64(heightPlus5.RevisionHeight), height, suite.headerTime, bothValSet, suite.valSet, bothSigners) currentTime = suite.now }, expFrozen: false, @@ -77,7 +79,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func(suite *TendermintTestSuite) { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), bothValSet.Hash()) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, bothValSet, bothValSet, bothSigners) + newHeader = chainATendermint.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, bothValSet, bothValSet, bothSigners) currentTime = suite.now }, expFrozen: false, @@ -89,7 +91,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) consStateHeight = heightMinus3 - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightMinus1.RevisionHeight), heightMinus3, suite.headerTime, bothValSet, suite.valSet, bothSigners) + newHeader = chainATendermint.CreateTMClientHeader(chainID, int64(heightMinus1.RevisionHeight), heightMinus3, suite.headerTime, bothValSet, suite.valSet, bothSigners) currentTime = suite.now }, expFrozen: false, @@ -101,7 +103,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { clientState = types.NewClientState(chainIDRevision1, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) consStateHeight = heightMinus3 - newHeader = suite.chainA.CreateTMClientHeader(chainIDRevision0, int64(height.RevisionHeight), heightMinus3, suite.headerTime, bothValSet, suite.valSet, bothSigners) + newHeader = chainATendermint.CreateTMClientHeader(chainIDRevision0, int64(height.RevisionHeight), heightMinus3, suite.headerTime, bothValSet, suite.valSet, bothSigners) currentTime = suite.now }, expPass: true, @@ -111,7 +113,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func(suite *TendermintTestSuite) { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, heightPlus1, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) + newHeader = chainATendermint.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) currentTime = suite.now ctx := suite.chainA.GetContext().WithBlockTime(currentTime) // Store the header's consensus state in client store before UpdateClient call @@ -125,7 +127,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func(suite *TendermintTestSuite) { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, heightPlus1, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) + newHeader = chainATendermint.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) currentTime = suite.now ctx := suite.chainA.GetContext().WithBlockTime(currentTime) // Change the consensus state of header and store in client store to create a conflict @@ -143,7 +145,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { // create an intermediate consensus state with the same time as the newHeader to create a time violation. // header time is after client time consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus5.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) + newHeader = chainATendermint.CreateTMClientHeader(chainID, int64(heightPlus5.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) currentTime = suite.now prevConsensusState := types.NewConsensusState(suite.headerTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) ctx := suite.chainA.GetContext().WithBlockTime(currentTime) @@ -161,7 +163,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { // create the next consensus state with the same time as the intermediate newHeader to create a time violation. // header time is after clientTime consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) + newHeader = chainATendermint.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) currentTime = suite.now nextConsensusState := types.NewConsensusState(suite.headerTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) ctx := suite.chainA.GetContext().WithBlockTime(currentTime) @@ -177,7 +179,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func(suite *TendermintTestSuite) { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader("ethermint", int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) + newHeader = chainATendermint.CreateTMClientHeader("ethermint", int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) currentTime = suite.now }, expFrozen: false, @@ -188,7 +190,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func(suite *TendermintTestSuite) { clientState = types.NewClientState(chainIDRevision0, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader(chainIDRevision1, 1, height, suite.headerTime, suite.valSet, suite.valSet, signers) + newHeader = chainATendermint.CreateTMClientHeader(chainIDRevision1, 1, height, suite.headerTime, suite.valSet, suite.valSet, signers) currentTime = suite.now }, expPass: false, @@ -198,7 +200,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func(suite *TendermintTestSuite) { clientState = types.NewClientState(chainIDRevision1, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader(chainIDRevision1, 3, height, suite.headerTime, suite.valSet, suite.valSet, signers) + newHeader = chainATendermint.CreateTMClientHeader(chainIDRevision1, 3, height, suite.headerTime, suite.valSet, suite.valSet, signers) currentTime = suite.now }, expFrozen: false, @@ -209,7 +211,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func(suite *TendermintTestSuite) { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, bothValSet, suite.valSet, bothSigners) + newHeader = chainATendermint.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, bothValSet, suite.valSet, bothSigners) currentTime = suite.now }, expFrozen: false, @@ -220,7 +222,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func(suite *TendermintTestSuite) { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), bothValSet.Hash()) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, bothValSet, signers) + newHeader = chainATendermint.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, bothValSet, signers) currentTime = suite.now }, expFrozen: false, @@ -231,7 +233,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func(suite *TendermintTestSuite) { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus5.RevisionHeight), height, suite.headerTime, altValSet, suite.valSet, altSigners) + newHeader = chainATendermint.CreateTMClientHeader(chainID, int64(heightPlus5.RevisionHeight), height, suite.headerTime, altValSet, suite.valSet, altSigners) currentTime = suite.now }, expFrozen: false, @@ -242,7 +244,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func(suite *TendermintTestSuite) { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus5.RevisionHeight), height, suite.headerTime, bothValSet, bothValSet, bothSigners) + newHeader = chainATendermint.CreateTMClientHeader(chainID, int64(heightPlus5.RevisionHeight), height, suite.headerTime, bothValSet, bothValSet, bothSigners) currentTime = suite.now }, expFrozen: false, @@ -253,7 +255,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func(suite *TendermintTestSuite) { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) + newHeader = chainATendermint.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) // make current time pass trusting period from last timestamp on clientstate currentTime = suite.now.Add(trustingPeriod) }, @@ -265,7 +267,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func(suite *TendermintTestSuite) { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers) + newHeader = chainATendermint.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers) currentTime = suite.now }, expFrozen: false, @@ -276,7 +278,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func(suite *TendermintTestSuite) { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.clientTime, suite.valSet, suite.valSet, signers) + newHeader = chainATendermint.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.clientTime, suite.valSet, suite.valSet, signers) currentTime = suite.now }, expFrozen: false, @@ -287,7 +289,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { setup: func(suite *TendermintTestSuite) { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) + newHeader = chainATendermint.CreateTMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) // cause new header to fail validatebasic by changing commit height to mismatch header height newHeader.SignedHeader.Commit.Height = revisionHeight - 1 currentTime = suite.now @@ -301,7 +303,7 @@ func (suite *TendermintTestSuite) TestCheckHeaderAndUpdateState() { clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.NewHeight(height.RevisionNumber, heightPlus5.RevisionHeight), commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) // Make new header at height less than latest client state - newHeader = suite.chainA.CreateTMClientHeader(chainID, int64(heightMinus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) + newHeader = chainATendermint.CreateTMClientHeader(chainID, int64(heightMinus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) currentTime = suite.now }, expFrozen: false, From 3a8e98bde941cba87094efb6e0a2cf28b416b92e Mon Sep 17 00:00:00 2001 From: Lior Zilpa Date: Sun, 20 Nov 2022 13:42:16 +0200 Subject: [PATCH 107/140] Move ClientConfig logic to tendermint chain --- testing/chain.go | 1 + testing/chain_tendermint.go | 31 +++++++++++++++++++++++++++++++ testing/config.go | 28 ---------------------------- testing/endpoint.go | 2 +- testing/values.go | 4 +--- 5 files changed, 34 insertions(+), 32 deletions(-) diff --git a/testing/chain.go b/testing/chain.go index b28691ea708..becf615a4a2 100644 --- a/testing/chain.go +++ b/testing/chain.go @@ -43,6 +43,7 @@ type TestChainClientI interface { UpdateCurrentHeaderTime(t time.Time) ClientConfigToState(ClientConfig ClientConfig) exported.ClientState GetConsensusState() exported.ConsensusState + NewConfig() ClientConfig } func NewTestChainClient(chain *TestChain, chainConsensusType string) TestChainClientI { diff --git a/testing/chain_tendermint.go b/testing/chain_tendermint.go index 1baa523c3fa..64c41e88a06 100644 --- a/testing/chain_tendermint.go +++ b/testing/chain_tendermint.go @@ -20,6 +20,26 @@ import ( ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" ) +var ( + // Default params variables used to create a TM client + DefaultTrustLevel ibctmtypes.Fraction = ibctmtypes.DefaultTrustLevel +) + +type TendermintConfig struct { + TrustLevel ibctmtypes.Fraction + TrustingPeriod time.Duration + UnbondingPeriod time.Duration + MaxClockDrift time.Duration + AllowUpdateAfterExpiry bool + AllowUpdateAfterMisbehaviour bool +} + +func (tmcfg *TendermintConfig) GetClientType() string { + return exported.Tendermint +} + +var _ ClientConfig = &TendermintConfig{} + // TestChainTendermint is a testing struct that 'wraps' a TestChain with the last TM Header, // the current ABCI header. type TestChainTendermint struct { @@ -52,6 +72,17 @@ func NewChainTendermintClient(tc *TestChain) *TestChainTendermint { return chain } +func (tc *TestChainTendermint) NewConfig() ClientConfig { + return &TendermintConfig{ + TrustLevel: DefaultTrustLevel, + TrustingPeriod: TrustingPeriod, + UnbondingPeriod: UnbondingPeriod, + MaxClockDrift: MaxClockDrift, + AllowUpdateAfterExpiry: false, + AllowUpdateAfterMisbehaviour: false, + } +} + // GetContext returns the current context for the application. func (chain *TestChainTendermint) GetContext() sdk.Context { return chain.TC.App.GetBaseApp().NewContext(false, chain.CurrentHeader) diff --git a/testing/config.go b/testing/config.go index a47dea1701b..49ba347c24b 100644 --- a/testing/config.go +++ b/testing/config.go @@ -1,12 +1,8 @@ package ibctesting import ( - "time" - connectiontypes "github.com/cosmos/ibc-go/v3/modules/core/03-connection/types" channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" - "github.com/cosmos/ibc-go/v3/modules/core/exported" - ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" "github.com/cosmos/ibc-go/v3/testing/mock" ) @@ -14,30 +10,6 @@ type ClientConfig interface { GetClientType() string } -type TendermintConfig struct { - TrustLevel ibctmtypes.Fraction - TrustingPeriod time.Duration - UnbondingPeriod time.Duration - MaxClockDrift time.Duration - AllowUpdateAfterExpiry bool - AllowUpdateAfterMisbehaviour bool -} - -func NewTendermintConfig() *TendermintConfig { - return &TendermintConfig{ - TrustLevel: DefaultTrustLevel, - TrustingPeriod: TrustingPeriod, - UnbondingPeriod: UnbondingPeriod, - MaxClockDrift: MaxClockDrift, - AllowUpdateAfterExpiry: false, - AllowUpdateAfterMisbehaviour: false, - } -} - -func (tmcfg *TendermintConfig) GetClientType() string { - return exported.Tendermint -} - type ConnectionConfig struct { DelayPeriod uint64 Version *connectiontypes.Version diff --git a/testing/endpoint.go b/testing/endpoint.go index fa13ec97063..1ea50f552d1 100644 --- a/testing/endpoint.go +++ b/testing/endpoint.go @@ -48,7 +48,7 @@ func NewEndpoint( func NewDefaultEndpoint(chain *TestChain) *Endpoint { return &Endpoint{ Chain: chain, - ClientConfig: NewTendermintConfig(), + ClientConfig: chain.TestChainClient.NewConfig(), ConnectionConfig: NewConnectionConfig(), ChannelConfig: NewChannelConfig(), } diff --git a/testing/values.go b/testing/values.go index 6bdf782c65a..477859cfb8c 100644 --- a/testing/values.go +++ b/testing/values.go @@ -12,7 +12,6 @@ import ( ibctransfertypes "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" connectiontypes "github.com/cosmos/ibc-go/v3/modules/core/03-connection/types" commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" - ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" "github.com/cosmos/ibc-go/v3/testing/mock" ) @@ -45,8 +44,7 @@ var ( DefaultOpenInitVersion *connectiontypes.Version // Default params variables used to create a TM client - DefaultTrustLevel ibctmtypes.Fraction = ibctmtypes.DefaultTrustLevel - TestCoin = sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)) + TestCoin = sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)) UpgradePath = []string{"upgrade", "upgradedIBCState"} From 11afc18c62396a3ba1d647b3cfc214d40084f4bf Mon Sep 17 00:00:00 2001 From: Lior Zilpa Date: Sun, 20 Nov 2022 14:52:13 +0200 Subject: [PATCH 108/140] Make ConstructUpdateClientHeader TestChainClientI method instead of switch-case --- testing/chain.go | 1 + testing/chain_tendermint.go | 6 ++++++ testing/endpoint.go | 11 +---------- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/testing/chain.go b/testing/chain.go index becf615a4a2..0a66039d57d 100644 --- a/testing/chain.go +++ b/testing/chain.go @@ -44,6 +44,7 @@ type TestChainClientI interface { ClientConfigToState(ClientConfig ClientConfig) exported.ClientState GetConsensusState() exported.ConsensusState NewConfig() ClientConfig + ConstructUpdateClientHeader(counterparty *TestChain, clientID string) (exported.Header, error) } func NewTestChainClient(chain *TestChain, chainConsensusType string) TestChainClientI { diff --git a/testing/chain_tendermint.go b/testing/chain_tendermint.go index 64c41e88a06..93764c6f455 100644 --- a/testing/chain_tendermint.go +++ b/testing/chain_tendermint.go @@ -112,6 +112,12 @@ func (chain *TestChainTendermint) NextBlock() { chain.BeginBlock() } +// ConstructUpdateClientHeader will construct a valid 07-tendermint Header to update the +// light client on the source chain. +func (chain *TestChainTendermint) ConstructUpdateClientHeader(counterparty *TestChain, clientID string) (exported.Header, error) { + return chain.ConstructUpdateTMClientHeader(counterparty, clientID) +} + // ConstructUpdateTMClientHeader will construct a valid 07-tendermint Header to update the // light client on the source chain. func (chain *TestChainTendermint) ConstructUpdateTMClientHeader(counterparty *TestChain, clientID string) (*ibctmtypes.Header, error) { diff --git a/testing/endpoint.go b/testing/endpoint.go index 1ea50f552d1..9ac2658cadb 100644 --- a/testing/endpoint.go +++ b/testing/endpoint.go @@ -106,16 +106,7 @@ func (endpoint *Endpoint) UpdateClient() (err error) { // ensure counterparty has committed state endpoint.Chain.Coordinator.CommitBlock(endpoint.Counterparty.Chain) - var header exported.Header - - switch endpoint.ClientConfig.GetClientType() { - case exported.Tendermint: - testChainTendermint := endpoint.Chain.TestChainClient.(*TestChainTendermint) - header, err = testChainTendermint.ConstructUpdateTMClientHeader(endpoint.Counterparty.Chain, endpoint.ClientID) - - default: - err = fmt.Errorf("client type %s is not supported", endpoint.ClientConfig.GetClientType()) - } + header, err := endpoint.Chain.TestChainClient.ConstructUpdateClientHeader(endpoint.Counterparty.Chain, endpoint.ClientID) if err != nil { return err From 309d690989e5c80b9116ffff0b5d8dcff3c88f93 Mon Sep 17 00:00:00 2001 From: Lior Zilpa Date: Mon, 21 Nov 2022 09:55:17 +0200 Subject: [PATCH 109/140] fix lint warning --- testing/chain_tendermint.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/chain_tendermint.go b/testing/chain_tendermint.go index 93764c6f455..a5a09b78f5c 100644 --- a/testing/chain_tendermint.go +++ b/testing/chain_tendermint.go @@ -72,7 +72,7 @@ func NewChainTendermintClient(tc *TestChain) *TestChainTendermint { return chain } -func (tc *TestChainTendermint) NewConfig() ClientConfig { +func (chain *TestChainTendermint) NewConfig() ClientConfig { return &TendermintConfig{ TrustLevel: DefaultTrustLevel, TrustingPeriod: TrustingPeriod, From ce25fe2bc8797f4e4ab8e84b302255eaea43f49a Mon Sep 17 00:00:00 2001 From: Lior Zilpa Date: Mon, 21 Nov 2022 12:43:59 +0200 Subject: [PATCH 110/140] Copy 07-tendermint, rename and create 01-dymint --- modules/core/exported/client.go | 3 + modules/core/types/codec.go | 2 + modules/light-clients/01-dymint/doc.go | 5 + modules/light-clients/01-dymint/module.go | 10 + .../01-dymint/types/client_state.go | 583 +++++ .../01-dymint/types/client_state_test.go | 883 ++++++++ .../light-clients/01-dymint/types/codec.go | 28 + .../01-dymint/types/consensus_state.go | 58 + .../01-dymint/types/consensus_state_test.go | 76 + .../01-dymint/types/dymint.pb.go | 1917 +++++++++++++++++ .../01-dymint/types/dymint_test.go | 110 + .../light-clients/01-dymint/types/errors.go | 26 + .../light-clients/01-dymint/types/fraction.go | 25 + .../light-clients/01-dymint/types/genesis.go | 22 + .../01-dymint/types/genesis_test.go | 89 + .../light-clients/01-dymint/types/header.go | 82 + .../01-dymint/types/header_test.go | 83 + .../01-dymint/types/misbehaviour.go | 132 ++ .../01-dymint/types/misbehaviour_handle.go | 144 ++ .../types/misbehaviour_handle_test.go | 432 ++++ .../01-dymint/types/misbehaviour_test.go | 247 +++ .../01-dymint/types/proposal_handle.go | 106 + .../01-dymint/types/proposal_handle_test.go | 371 ++++ .../01-dymint/types/self_client.go | 112 + .../light-clients/01-dymint/types/store.go | 371 ++++ .../01-dymint/types/store_test.go | 194 ++ .../light-clients/01-dymint/types/update.go | 263 +++ .../01-dymint/types/update_test.go | 458 ++++ .../light-clients/01-dymint/types/upgrade.go | 156 ++ .../01-dymint/types/upgrade_test.go | 482 +++++ proto/ibc/lightclients/dymint/dymint.proto | 115 + testing/chain.go | 2 + testing/chain_dymint.go | 259 +++ testing/simapp/app.go | 3 + 34 files changed, 7849 insertions(+) create mode 100644 modules/light-clients/01-dymint/doc.go create mode 100644 modules/light-clients/01-dymint/module.go create mode 100644 modules/light-clients/01-dymint/types/client_state.go create mode 100644 modules/light-clients/01-dymint/types/client_state_test.go create mode 100644 modules/light-clients/01-dymint/types/codec.go create mode 100644 modules/light-clients/01-dymint/types/consensus_state.go create mode 100644 modules/light-clients/01-dymint/types/consensus_state_test.go create mode 100644 modules/light-clients/01-dymint/types/dymint.pb.go create mode 100644 modules/light-clients/01-dymint/types/dymint_test.go create mode 100644 modules/light-clients/01-dymint/types/errors.go create mode 100644 modules/light-clients/01-dymint/types/fraction.go create mode 100644 modules/light-clients/01-dymint/types/genesis.go create mode 100644 modules/light-clients/01-dymint/types/genesis_test.go create mode 100644 modules/light-clients/01-dymint/types/header.go create mode 100644 modules/light-clients/01-dymint/types/header_test.go create mode 100644 modules/light-clients/01-dymint/types/misbehaviour.go create mode 100644 modules/light-clients/01-dymint/types/misbehaviour_handle.go create mode 100644 modules/light-clients/01-dymint/types/misbehaviour_handle_test.go create mode 100644 modules/light-clients/01-dymint/types/misbehaviour_test.go create mode 100644 modules/light-clients/01-dymint/types/proposal_handle.go create mode 100644 modules/light-clients/01-dymint/types/proposal_handle_test.go create mode 100644 modules/light-clients/01-dymint/types/self_client.go create mode 100644 modules/light-clients/01-dymint/types/store.go create mode 100644 modules/light-clients/01-dymint/types/store_test.go create mode 100644 modules/light-clients/01-dymint/types/update.go create mode 100644 modules/light-clients/01-dymint/types/update_test.go create mode 100644 modules/light-clients/01-dymint/types/upgrade.go create mode 100644 modules/light-clients/01-dymint/types/upgrade_test.go create mode 100644 proto/ibc/lightclients/dymint/dymint.proto create mode 100644 testing/chain_dymint.go diff --git a/modules/core/exported/client.go b/modules/core/exported/client.go index d11cf2002a6..c6e7acdf822 100644 --- a/modules/core/exported/client.go +++ b/modules/core/exported/client.go @@ -21,6 +21,9 @@ const ( // Tendermint is used to indicate that the client uses the Tendermint Consensus Algorithm. Tendermint string = "07-tendermint" + // Dymint is used to indicate that the client is a Dymension rollapp. + Dymint string = "01-dymint" + // Localhost is the client type for a localhost client. It is also used as the clientID // for the localhost client. Localhost string = "09-localhost" diff --git a/modules/core/types/codec.go b/modules/core/types/codec.go index 5caf105514e..23894122fa6 100644 --- a/modules/core/types/codec.go +++ b/modules/core/types/codec.go @@ -7,6 +7,7 @@ import ( connectiontypes "github.com/cosmos/ibc-go/v3/modules/core/03-connection/types" channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" + ibcdmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" solomachinetypes "github.com/cosmos/ibc-go/v3/modules/light-clients/06-solomachine/types" ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" localhosttypes "github.com/cosmos/ibc-go/v3/modules/light-clients/09-localhost/types" @@ -17,6 +18,7 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) { clienttypes.RegisterInterfaces(registry) connectiontypes.RegisterInterfaces(registry) channeltypes.RegisterInterfaces(registry) + ibcdmtypes.RegisterInterfaces(registry) solomachinetypes.RegisterInterfaces(registry) ibctmtypes.RegisterInterfaces(registry) localhosttypes.RegisterInterfaces(registry) diff --git a/modules/light-clients/01-dymint/doc.go b/modules/light-clients/01-dymint/doc.go new file mode 100644 index 00000000000..e0ac5ac9907 --- /dev/null +++ b/modules/light-clients/01-dymint/doc.go @@ -0,0 +1,5 @@ +/* +Package dymint implements a concrete `ConsensusState`, `Header`, +`Misbehaviour` and `Equivocation` types for the dymint consensus light client. +*/ +package dymint diff --git a/modules/light-clients/01-dymint/module.go b/modules/light-clients/01-dymint/module.go new file mode 100644 index 00000000000..8f9e29b0e28 --- /dev/null +++ b/modules/light-clients/01-dymint/module.go @@ -0,0 +1,10 @@ +package dymint + +import ( + "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" +) + +// Name returns the IBC client name +func Name() string { + return types.SubModuleName +} diff --git a/modules/light-clients/01-dymint/types/client_state.go b/modules/light-clients/01-dymint/types/client_state.go new file mode 100644 index 00000000000..92087f0ed6d --- /dev/null +++ b/modules/light-clients/01-dymint/types/client_state.go @@ -0,0 +1,583 @@ +package types + +import ( + "strings" + "time" + + ics23 "github.com/confio/ics23/go" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/tendermint/tendermint/light" + tmtypes "github.com/tendermint/tendermint/types" + + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v3/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" + commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v3/modules/core/24-host" + "github.com/cosmos/ibc-go/v3/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, allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour bool, +) *ClientState { + return &ClientState{ + ChainId: chainID, + TrustLevel: trustLevel, + TrustingPeriod: trustingPeriod, + UnbondingPeriod: ubdPeriod, + MaxClockDrift: maxClockDrift, + LatestHeight: latestHeight, + FrozenHeight: clienttypes.ZeroHeight(), + ProofSpecs: specs, + UpgradePath: upgradePath, + AllowUpdateAfterExpiry: allowUpdateAfterExpiry, + AllowUpdateAfterMisbehaviour: allowUpdateAfterMisbehaviour, + } +} + +// GetChainID returns the chain-id +func (cs ClientState) GetChainID() string { + return cs.ChainId +} + +// ClientType is dymint. +func (cs ClientState) ClientType() string { + return exported.Dymint +} + +// GetLatestHeight returns latest block height. +func (cs ClientState) GetLatestHeight() exported.Height { + return cs.LatestHeight +} + +// Status returns the status of the dymint 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 sdk.KVStore, + cdc codec.BinaryCodec, +) exported.Status { + if !cs.FrozenHeight.IsZero() { + return exported.Frozen + } + + // get latest consensus state from clientStore to check for expiry + consState, err := GetConsensusState(clientStore, cdc, cs.GetLatestHeight()) + if err != nil { + // 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 sdkerrors.Wrap(ErrInvalidChainID, "chain id cannot be empty string") + } + + // NOTE: the value of tmtypes.MaxChainIDLen may change in the future. + // If this occurs, the code here must account for potential difference + // between the dymint version being run by the counterparty chain + // and the dymint version used by this light client. + // https://github.com/cosmos/ibc-go/issues/177 + if len(cs.ChainId) > tmtypes.MaxChainIDLen { + return sdkerrors.Wrapf(ErrInvalidChainID, "chainID is too long; got: %d, max: %d", len(cs.ChainId), tmtypes.MaxChainIDLen) + } + + if err := light.ValidateTrustLevel(cs.TrustLevel.ToDymint()); err != nil { + return err + } + if cs.TrustingPeriod == 0 { + return sdkerrors.Wrap(ErrInvalidTrustingPeriod, "trusting period cannot be zero") + } + if cs.UnbondingPeriod == 0 { + return sdkerrors.Wrap(ErrInvalidUnbondingPeriod, "unbonding period cannot be zero") + } + if cs.MaxClockDrift == 0 { + return sdkerrors.Wrap(ErrInvalidMaxClockDrift, "max clock drift cannot be zero") + } + + // the latest height revision number must match the chain id revision number + if cs.LatestHeight.RevisionNumber != clienttypes.ParseChainID(cs.ChainId) { + return sdkerrors.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 sdkerrors.Wrapf(ErrInvalidHeaderHeight, "dymint client's latest height revision height cannot be zero") + } + if cs.TrustingPeriod >= cs.UnbondingPeriod { + return sdkerrors.Wrapf( + ErrInvalidTrustingPeriod, + "trusting period (%s) should be < unbonding period (%s)", cs.TrustingPeriod, cs.UnbondingPeriod, + ) + } + + if cs.ProofSpecs == nil { + return sdkerrors.Wrap(ErrInvalidProofSpecs, "proof specs cannot be nil for tm client") + } + for i, spec := range cs.ProofSpecs { + if spec == nil { + return sdkerrors.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 sdkerrors.Wrapf(clienttypes.ErrInvalidClient, "key in upgrade path at index %d cannot be empty", i) + } + } + + return nil +} + +// GetProofSpecs returns the format the client expects for proof verification +// as a string array specifying the proof type for each position in chained proof +func (cs ClientState) GetProofSpecs() []*ics23.ProofSpec { + return cs.ProofSpecs +} + +// ZeroCustomFields returns a ClientState that is a copy of the current ClientState +// with all client customizable fields zeroed out +func (cs ClientState) ZeroCustomFields() exported.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 will check that initial consensus state is a Dymint consensus state +// and will store ProcessedTime for initial consensus state as ctx.BlockTime() +func (cs ClientState) Initialize(ctx sdk.Context, _ codec.BinaryCodec, clientStore sdk.KVStore, consState exported.ConsensusState) error { + if _, ok := consState.(*ConsensusState); !ok { + return sdkerrors.Wrapf(clienttypes.ErrInvalidConsensus, "invalid initial consensus state. expected type: %T, got: %T", + &ConsensusState{}, consState) + } + // set metadata for initial consensus state. + setConsensusMetadata(ctx, clientStore, cs.GetLatestHeight()) + return nil +} + +// VerifyClientState verifies a proof of the client state of the running chain +// stored on the target machine +func (cs ClientState) VerifyClientState( + store sdk.KVStore, + cdc codec.BinaryCodec, + height exported.Height, + prefix exported.Prefix, + counterpartyClientIdentifier string, + proof []byte, + clientState exported.ClientState, +) error { + merkleProof, provingConsensusState, err := produceVerificationArgs(store, cdc, cs, height, prefix, proof) + if err != nil { + return err + } + + clientPrefixedPath := commitmenttypes.NewMerklePath(host.FullClientStatePath(counterpartyClientIdentifier)) + path, err := commitmenttypes.ApplyPrefix(prefix, clientPrefixedPath) + if err != nil { + return err + } + + if clientState == nil { + return sdkerrors.Wrap(clienttypes.ErrInvalidClient, "client state cannot be empty") + } + + _, ok := clientState.(*ClientState) + if !ok { + return sdkerrors.Wrapf(clienttypes.ErrInvalidClient, "invalid client type %T, expected %T", clientState, &ClientState{}) + } + + bz, err := cdc.MarshalInterface(clientState) + if err != nil { + return err + } + + return merkleProof.VerifyMembership(cs.ProofSpecs, provingConsensusState.GetRoot(), path, bz) +} + +// VerifyClientConsensusState verifies a proof of the consensus state of the +// Dymint client stored on the target machine. +func (cs ClientState) VerifyClientConsensusState( + store sdk.KVStore, + cdc codec.BinaryCodec, + height exported.Height, + counterpartyClientIdentifier string, + consensusHeight exported.Height, + prefix exported.Prefix, + proof []byte, + consensusState exported.ConsensusState, +) error { + merkleProof, provingConsensusState, err := produceVerificationArgs(store, cdc, cs, height, prefix, proof) + if err != nil { + return err + } + + clientPrefixedPath := commitmenttypes.NewMerklePath(host.FullConsensusStatePath(counterpartyClientIdentifier, consensusHeight)) + path, err := commitmenttypes.ApplyPrefix(prefix, clientPrefixedPath) + if err != nil { + return err + } + + if consensusState == nil { + return sdkerrors.Wrap(clienttypes.ErrInvalidConsensus, "consensus state cannot be empty") + } + + _, ok := consensusState.(*ConsensusState) + if !ok { + return sdkerrors.Wrapf(clienttypes.ErrInvalidConsensus, "invalid consensus type %T, expected %T", consensusState, &ConsensusState{}) + } + + bz, err := cdc.MarshalInterface(consensusState) + if err != nil { + return err + } + + if err := merkleProof.VerifyMembership(cs.ProofSpecs, provingConsensusState.GetRoot(), path, bz); err != nil { + return err + } + + return nil +} + +// VerifyConnectionState verifies a proof of the connection state of the +// specified connection end stored on the target machine. +func (cs ClientState) VerifyConnectionState( + store sdk.KVStore, + cdc codec.BinaryCodec, + height exported.Height, + prefix exported.Prefix, + proof []byte, + connectionID string, + connectionEnd exported.ConnectionI, +) error { + merkleProof, consensusState, err := produceVerificationArgs(store, cdc, cs, height, prefix, proof) + if err != nil { + return err + } + + connectionPath := commitmenttypes.NewMerklePath(host.ConnectionPath(connectionID)) + path, err := commitmenttypes.ApplyPrefix(prefix, connectionPath) + if err != nil { + return err + } + + connection, ok := connectionEnd.(connectiontypes.ConnectionEnd) + if !ok { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "invalid connection type %T", connectionEnd) + } + + bz, err := cdc.Marshal(&connection) + if err != nil { + return err + } + + if err := merkleProof.VerifyMembership(cs.ProofSpecs, consensusState.GetRoot(), path, bz); err != nil { + return err + } + + 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 (cs ClientState) VerifyChannelState( + store sdk.KVStore, + cdc codec.BinaryCodec, + height exported.Height, + prefix exported.Prefix, + proof []byte, + portID, + channelID string, + channel exported.ChannelI, +) error { + merkleProof, consensusState, err := produceVerificationArgs(store, cdc, cs, height, prefix, proof) + if err != nil { + return err + } + + channelPath := commitmenttypes.NewMerklePath(host.ChannelPath(portID, channelID)) + path, err := commitmenttypes.ApplyPrefix(prefix, channelPath) + if err != nil { + return err + } + + channelEnd, ok := channel.(channeltypes.Channel) + if !ok { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "invalid channel type %T", channel) + } + + bz, err := cdc.Marshal(&channelEnd) + if err != nil { + return err + } + + if err := merkleProof.VerifyMembership(cs.ProofSpecs, consensusState.GetRoot(), path, bz); err != nil { + return err + } + + return nil +} + +// VerifyPacketCommitment verifies a proof of an outgoing packet commitment at +// the specified port, specified channel, and specified sequence. +func (cs ClientState) VerifyPacketCommitment( + ctx sdk.Context, + store sdk.KVStore, + cdc codec.BinaryCodec, + height exported.Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + prefix exported.Prefix, + proof []byte, + portID, + channelID string, + sequence uint64, + commitmentBytes []byte, +) error { + merkleProof, consensusState, err := produceVerificationArgs(store, cdc, cs, height, prefix, proof) + if err != nil { + return err + } + + // check delay period has passed + if err := verifyDelayPeriodPassed(ctx, store, height, delayTimePeriod, delayBlockPeriod); err != nil { + return err + } + + commitmentPath := commitmenttypes.NewMerklePath(host.PacketCommitmentPath(portID, channelID, sequence)) + path, err := commitmenttypes.ApplyPrefix(prefix, commitmentPath) + if err != nil { + return err + } + + if err := merkleProof.VerifyMembership(cs.ProofSpecs, consensusState.GetRoot(), path, commitmentBytes); err != nil { + return err + } + + return nil +} + +// VerifyPacketAcknowledgement verifies a proof of an incoming packet +// acknowledgement at the specified port, specified channel, and specified sequence. +func (cs ClientState) VerifyPacketAcknowledgement( + ctx sdk.Context, + store sdk.KVStore, + cdc codec.BinaryCodec, + height exported.Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + prefix exported.Prefix, + proof []byte, + portID, + channelID string, + sequence uint64, + acknowledgement []byte, +) error { + merkleProof, consensusState, err := produceVerificationArgs(store, cdc, cs, height, prefix, proof) + if err != nil { + return err + } + + // check delay period has passed + if err := verifyDelayPeriodPassed(ctx, store, height, delayTimePeriod, delayBlockPeriod); err != nil { + return err + } + + ackPath := commitmenttypes.NewMerklePath(host.PacketAcknowledgementPath(portID, channelID, sequence)) + path, err := commitmenttypes.ApplyPrefix(prefix, ackPath) + if err != nil { + return err + } + + if err := merkleProof.VerifyMembership(cs.ProofSpecs, consensusState.GetRoot(), path, channeltypes.CommitAcknowledgement(acknowledgement)); err != nil { + return err + } + + return nil +} + +// VerifyPacketReceiptAbsence verifies a proof of the absence of an +// incoming packet receipt at the specified port, specified channel, and +// specified sequence. +func (cs ClientState) VerifyPacketReceiptAbsence( + ctx sdk.Context, + store sdk.KVStore, + cdc codec.BinaryCodec, + height exported.Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + prefix exported.Prefix, + proof []byte, + portID, + channelID string, + sequence uint64, +) error { + merkleProof, consensusState, err := produceVerificationArgs(store, cdc, cs, height, prefix, proof) + if err != nil { + return err + } + + // check delay period has passed + if err := verifyDelayPeriodPassed(ctx, store, height, delayTimePeriod, delayBlockPeriod); err != nil { + return err + } + + receiptPath := commitmenttypes.NewMerklePath(host.PacketReceiptPath(portID, channelID, sequence)) + path, err := commitmenttypes.ApplyPrefix(prefix, receiptPath) + if err != nil { + return err + } + + if err := merkleProof.VerifyNonMembership(cs.ProofSpecs, consensusState.GetRoot(), path); err != nil { + return err + } + + return nil +} + +// VerifyNextSequenceRecv verifies a proof of the next sequence number to be +// received of the specified channel at the specified port. +func (cs ClientState) VerifyNextSequenceRecv( + ctx sdk.Context, + store sdk.KVStore, + cdc codec.BinaryCodec, + height exported.Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + prefix exported.Prefix, + proof []byte, + portID, + channelID string, + nextSequenceRecv uint64, +) error { + merkleProof, consensusState, err := produceVerificationArgs(store, cdc, cs, height, prefix, proof) + if err != nil { + return err + } + + // check delay period has passed + if err := verifyDelayPeriodPassed(ctx, store, height, delayTimePeriod, delayBlockPeriod); err != nil { + return err + } + + nextSequenceRecvPath := commitmenttypes.NewMerklePath(host.NextSequenceRecvPath(portID, channelID)) + path, err := commitmenttypes.ApplyPrefix(prefix, nextSequenceRecvPath) + if err != nil { + return err + } + + bz := sdk.Uint64ToBigEndian(nextSequenceRecv) + + if err := merkleProof.VerifyMembership(cs.ProofSpecs, consensusState.GetRoot(), path, bz); err != nil { + return err + } + + return nil +} + +// 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 sdk.KVStore, proofHeight exported.Height, delayTimePeriod, delayBlockPeriod uint64) error { + // check that executing chain's timestamp has passed consensusState's processed time + delay time period + processedTime, ok := GetProcessedTime(store, proofHeight) + if !ok { + return sdkerrors.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 sdkerrors.Wrapf(ErrDelayPeriodNotPassed, "cannot verify packet until time: %d, current time: %d", + validTime, currentTimestamp) + } + // check that executing chain's height has passed consensusState's processed height + delay block period + processedHeight, ok := GetProcessedHeight(store, proofHeight) + if !ok { + return sdkerrors.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 sdkerrors.Wrapf(ErrDelayPeriodNotPassed, "cannot verify packet until height: %s, current height: %s", + validHeight, currentHeight) + } + return nil +} + +// produceVerificationArgs perfoms the basic checks on the arguments that are +// shared between the verification functions and returns the unmarshalled +// merkle proof, the consensus state and an error if one occurred. +func produceVerificationArgs( + store sdk.KVStore, + cdc codec.BinaryCodec, + cs ClientState, + height exported.Height, + prefix exported.Prefix, + proof []byte, +) (merkleProof commitmenttypes.MerkleProof, consensusState *ConsensusState, err error) { + if cs.GetLatestHeight().LT(height) { + return commitmenttypes.MerkleProof{}, nil, sdkerrors.Wrapf( + sdkerrors.ErrInvalidHeight, + "client state height < proof height (%d < %d), please ensure the client has been updated", cs.GetLatestHeight(), height, + ) + } + + if prefix == nil { + return commitmenttypes.MerkleProof{}, nil, sdkerrors.Wrap(commitmenttypes.ErrInvalidPrefix, "prefix cannot be empty") + } + + _, ok := prefix.(*commitmenttypes.MerklePrefix) + if !ok { + return commitmenttypes.MerkleProof{}, nil, sdkerrors.Wrapf(commitmenttypes.ErrInvalidPrefix, "invalid prefix type %T, expected *MerklePrefix", prefix) + } + + if proof == nil { + return commitmenttypes.MerkleProof{}, nil, sdkerrors.Wrap(commitmenttypes.ErrInvalidProof, "proof cannot be empty") + } + + if err = cdc.Unmarshal(proof, &merkleProof); err != nil { + return commitmenttypes.MerkleProof{}, nil, sdkerrors.Wrap(commitmenttypes.ErrInvalidProof, "failed to unmarshal proof into commitment merkle proof") + } + + consensusState, err = GetConsensusState(store, cdc, height) + if err != nil { + return commitmenttypes.MerkleProof{}, nil, sdkerrors.Wrap(err, "please ensure the proof was constructed against a height that exists on the client") + } + + return merkleProof, consensusState, nil +} diff --git a/modules/light-clients/01-dymint/types/client_state_test.go b/modules/light-clients/01-dymint/types/client_state_test.go new file mode 100644 index 00000000000..0fc6bba9e6a --- /dev/null +++ b/modules/light-clients/01-dymint/types/client_state_test.go @@ -0,0 +1,883 @@ +package types_test + +import ( + "time" + + ics23 "github.com/confio/ics23/go" + + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" + commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v3/modules/core/24-host" + "github.com/cosmos/ibc-go/v3/modules/core/exported" + "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" + ibctesting "github.com/cosmos/ibc-go/v3/testing" + ibcmock "github.com/cosmos/ibc-go/v3/testing/mock" +) + +const ( + testClientID = "clientidone" + testConnectionID = "connectionid" + testPortID = "testportid" + testChannelID = "testchannelid" + testSequence = 1 + + // Do not change the length of these variables + fiftyCharChainID = "12345678901234567890123456789012345678901234567890" + fiftyOneCharChainID = "123456789012345678901234567890123456789012345678901" +) + +var ( + invalidProof = []byte("invalid proof") +) + +func (suite *DymintTestSuite) TestStatus() { + var ( + path *ibctesting.Path + clientState *types.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() { + clientState.LatestHeight = clientState.LatestHeight.Increment().(clienttypes.Height) + path.EndpointA.SetClientState(clientState) + }, exported.Expired}, + {"client status is expired", func() { + suite.coordinator.IncrementTimeBy(clientState.TrustingPeriod) + }, exported.Expired}, + } + + for _, tc := range testCases { + path = ibctesting.NewPath(suite.chainA, suite.chainB) + suite.coordinator.SetupClients(path) + + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + clientState = path.EndpointA.GetClientState().(*types.ClientState) + + tc.malleate() + + status := clientState.Status(suite.chainA.GetContext(), clientStore, suite.chainA.App.AppCodec()) + suite.Require().Equal(tc.expStatus, status) + + } +} + +func (suite *DymintTestSuite) TestValidate() { + testCases := []struct { + name string + clientState *types.ClientState + expPass bool + }{ + { + name: "valid client", + clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + expPass: true, + }, + { + name: "valid client with nil upgrade path", + clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), nil, false, false), + expPass: true, + }, + { + name: "invalid chainID", + clientState: types.NewClientState(" ", types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + expPass: false, + }, + { + // 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 failed for chainID of length 50! ", + clientState: types.NewClientState(fiftyCharChainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + expPass: true, + }, + { + // 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 did not fail for chainID of length 51! ", + clientState: types.NewClientState(fiftyOneCharChainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + expPass: false, + }, + { + name: "invalid trust level", + clientState: types.NewClientState(chainID, types.Fraction{Numerator: 0, Denominator: 1}, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + expPass: false, + }, + { + name: "invalid trusting period", + clientState: types.NewClientState(chainID, types.DefaultTrustLevel, 0, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + expPass: false, + }, + { + name: "invalid unbonding period", + clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, 0, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + expPass: false, + }, + { + name: "invalid max clock drift", + clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, 0, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + expPass: false, + }, + { + name: "invalid revision number", + clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + expPass: false, + }, + { + name: "invalid revision height", + clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.ZeroHeight(), commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + expPass: false, + }, + { + name: "trusting period not less than unbonding period", + clientState: types.NewClientState(chainID, types.DefaultTrustLevel, ubdPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + expPass: false, + }, + { + name: "proof specs is nil", + clientState: types.NewClientState(chainID, types.DefaultTrustLevel, ubdPeriod, ubdPeriod, maxClockDrift, height, nil, upgradePath, false, false), + expPass: false, + }, + { + name: "proof specs contains nil", + clientState: types.NewClientState(chainID, types.DefaultTrustLevel, ubdPeriod, ubdPeriod, maxClockDrift, height, []*ics23.ProofSpec{ics23.TendermintSpec, nil}, upgradePath, false, false), + expPass: false, + }, + } + + for _, tc := range testCases { + err := tc.clientState.Validate() + if tc.expPass { + suite.Require().NoError(err, tc.name) + } else { + suite.Require().Error(err, tc.name) + } + } +} + +func (suite *DymintTestSuite) TestInitialize() { + + testCases := []struct { + name string + consensusState exported.ConsensusState + expPass bool + }{ + { + name: "valid consensus", + consensusState: &types.ConsensusState{}, + expPass: true, + }, + { + name: "invalid consensus: consensus state is solomachine consensus", + consensusState: ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2).ConsensusState(), + expPass: false, + }, + } + + path := ibctesting.NewPath(suite.chainA, suite.chainB) + err := path.EndpointA.CreateClient() + suite.Require().NoError(err) + + clientState := suite.chainA.GetClientState(path.EndpointA.ClientID) + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + + for _, tc := range testCases { + err := clientState.Initialize(suite.chainA.GetContext(), suite.chainA.Codec, store, tc.consensusState) + if tc.expPass { + suite.Require().NoError(err, "valid case returned an error") + } else { + suite.Require().Error(err, "invalid case didn't return an error") + } + } +} + +func (suite *DymintTestSuite) TestVerifyClientConsensusState() { + testCases := []struct { + name string + clientState *types.ClientState + consensusState *types.ConsensusState + prefix commitmenttypes.MerklePrefix + proof []byte + expPass bool + }{ + // FIXME: uncomment + // { + // name: "successful verification", + // clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + // consensusState: types.ConsensusState{ + // Root: commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), + // }, + // prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), + // expPass: true, + // }, + { + name: "ApplyPrefix failed", + clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + consensusState: &types.ConsensusState{ + Root: commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), + }, + prefix: commitmenttypes.MerklePrefix{}, + expPass: false, + }, + { + name: "latest client height < height", + clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + consensusState: &types.ConsensusState{ + Root: commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), + }, + prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), + expPass: false, + }, + { + name: "proof verification failed", + clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + consensusState: &types.ConsensusState{ + Root: commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), + NextValidatorsHash: suite.valsHash, + }, + prefix: commitmenttypes.NewMerklePrefix([]byte("ibc")), + proof: []byte{}, + expPass: false, + }, + } + + for i, tc := range testCases { + tc := tc + + err := tc.clientState.VerifyClientConsensusState( + nil, suite.cdc, height, "chainA", tc.clientState.LatestHeight, tc.prefix, tc.proof, tc.consensusState, + ) + + if tc.expPass { + 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) + } + } +} + +// test verification of the connection on chainB being represented in the +// light client on chainA +func (suite *DymintTestSuite) TestVerifyConnectionState() { + var ( + clientState *types.ClientState + proof []byte + proofHeight exported.Height + prefix commitmenttypes.MerklePrefix + ) + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "successful verification", func() {}, true, + }, + { + "ApplyPrefix failed", func() { + prefix = commitmenttypes.MerklePrefix{} + }, false, + }, + { + "latest client height < height", func() { + proofHeight = clientState.LatestHeight.Increment() + }, false, + }, + { + "proof verification failed", func() { + proof = invalidProof + }, false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + // setup testing conditions + path := ibctesting.NewPath(suite.chainA, suite.chainB) + suite.coordinator.Setup(path) + connection := path.EndpointB.GetConnection() + + var ok bool + clientStateI := suite.chainA.GetClientState(path.EndpointA.ClientID) + clientState, ok = clientStateI.(*types.ClientState) + suite.Require().True(ok) + + prefix = suite.chainB.GetPrefix() + + // make connection proof + connectionKey := host.ConnectionKey(path.EndpointB.ConnectionID) + proof, proofHeight = suite.chainB.QueryProof(connectionKey) + + tc.malleate() // make changes as necessary + + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + + err := clientState.VerifyConnectionState( + store, suite.chainA.Codec, proofHeight, &prefix, proof, path.EndpointB.ConnectionID, connection, + ) + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} + +// test verification of the channel on chainB being represented in the light +// client on chainA +func (suite *DymintTestSuite) TestVerifyChannelState() { + var ( + clientState *types.ClientState + proof []byte + proofHeight exported.Height + prefix commitmenttypes.MerklePrefix + ) + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "successful verification", func() {}, true, + }, + { + "ApplyPrefix failed", func() { + prefix = commitmenttypes.MerklePrefix{} + }, false, + }, + { + "latest client height < height", func() { + proofHeight = clientState.LatestHeight.Increment() + }, false, + }, + { + "proof verification failed", func() { + proof = invalidProof + }, false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + // setup testing conditions + path := ibctesting.NewPath(suite.chainA, suite.chainB) + suite.coordinator.Setup(path) + channel := path.EndpointB.GetChannel() + + var ok bool + clientStateI := suite.chainA.GetClientState(path.EndpointA.ClientID) + clientState, ok = clientStateI.(*types.ClientState) + suite.Require().True(ok) + + prefix = suite.chainB.GetPrefix() + + // make channel proof + channelKey := host.ChannelKey(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + proof, proofHeight = suite.chainB.QueryProof(channelKey) + + tc.malleate() // make changes as necessary + + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + + err := clientState.VerifyChannelState( + store, suite.chainA.Codec, proofHeight, &prefix, proof, + path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, channel, + ) + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} + +// test verification of the packet commitment on chainB being represented +// in the light client on chainA. A send from chainB to chainA is simulated. +func (suite *DymintTestSuite) TestVerifyPacketCommitment() { + var ( + clientState *types.ClientState + proof []byte + delayTimePeriod uint64 + delayBlockPeriod uint64 + proofHeight exported.Height + prefix commitmenttypes.MerklePrefix + ) + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "successful verification", func() {}, true, + }, + { + name: "delay time period has passed", + malleate: func() { + delayTimePeriod = uint64(time.Second.Nanoseconds()) + }, + expPass: true, + }, + { + name: "delay time period has not passed", + malleate: func() { + delayTimePeriod = uint64(time.Hour.Nanoseconds()) + }, + expPass: false, + }, + { + name: "delay block period has passed", + malleate: func() { + delayBlockPeriod = 1 + }, + expPass: true, + }, + { + name: "delay block period has not passed", + malleate: func() { + delayBlockPeriod = 1000 + }, + expPass: false, + }, + + { + "ApplyPrefix failed", func() { + prefix = commitmenttypes.MerklePrefix{} + }, false, + }, + { + "latest client height < height", func() { + proofHeight = clientState.LatestHeight.Increment() + }, false, + }, + { + "proof verification failed", func() { + proof = invalidProof + }, false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + // setup testing conditions + path := ibctesting.NewPath(suite.chainA, suite.chainB) + suite.coordinator.Setup(path) + packet := channeltypes.NewPacket(ibctesting.MockPacketData, 1, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, clienttypes.NewHeight(0, 100), 0) + err := path.EndpointB.SendPacket(packet) + suite.Require().NoError(err) + + var ok bool + clientStateI := suite.chainA.GetClientState(path.EndpointA.ClientID) + clientState, ok = clientStateI.(*types.ClientState) + suite.Require().True(ok) + + prefix = suite.chainB.GetPrefix() + + // make packet commitment proof + packetKey := host.PacketCommitmentKey(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + proof, proofHeight = path.EndpointB.QueryProof(packetKey) + + // reset time and block delays to 0, malleate may change to a specific non-zero value. + delayTimePeriod = 0 + delayBlockPeriod = 0 + tc.malleate() // make changes as necessary + + ctx := suite.chainA.GetContext() + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, path.EndpointA.ClientID) + + commitment := channeltypes.CommitPacket(suite.chainA.App.GetIBCKeeper().Codec(), packet) + err = clientState.VerifyPacketCommitment( + ctx, store, suite.chainA.Codec, proofHeight, delayTimePeriod, delayBlockPeriod, &prefix, proof, + packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence(), commitment, + ) + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} + +// test verification of the acknowledgement on chainB being represented +// in the light client on chainA. A send and ack from chainA to chainB +// is simulated. +func (suite *DymintTestSuite) TestVerifyPacketAcknowledgement() { + var ( + clientState *types.ClientState + proof []byte + delayTimePeriod uint64 + delayBlockPeriod uint64 + proofHeight exported.Height + prefix commitmenttypes.MerklePrefix + ) + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "successful verification", func() {}, true, + }, + { + name: "delay time period has passed", + malleate: func() { + delayTimePeriod = uint64(time.Second.Nanoseconds()) + }, + expPass: true, + }, + { + name: "delay time period has not passed", + malleate: func() { + delayTimePeriod = uint64(time.Hour.Nanoseconds()) + }, + expPass: false, + }, + { + name: "delay block period has passed", + malleate: func() { + delayBlockPeriod = 1 + }, + expPass: true, + }, + { + name: "delay block period has not passed", + malleate: func() { + delayBlockPeriod = 10 + }, + expPass: false, + }, + + { + "ApplyPrefix failed", func() { + prefix = commitmenttypes.MerklePrefix{} + }, false, + }, + { + "latest client height < height", func() { + proofHeight = clientState.LatestHeight.Increment() + }, false, + }, + { + "proof verification failed", func() { + proof = invalidProof + }, false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + // setup testing conditions + path := ibctesting.NewPath(suite.chainA, suite.chainB) + suite.coordinator.Setup(path) + packet := channeltypes.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.NewHeight(0, 100), 0) + + // send packet + err := path.EndpointA.SendPacket(packet) + suite.Require().NoError(err) + + // write receipt and ack + err = path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + + var ok bool + clientStateI := suite.chainA.GetClientState(path.EndpointA.ClientID) + clientState, ok = clientStateI.(*types.ClientState) + suite.Require().True(ok) + + prefix = suite.chainB.GetPrefix() + + // make packet acknowledgement proof + acknowledgementKey := host.PacketAcknowledgementKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + proof, proofHeight = suite.chainB.QueryProof(acknowledgementKey) + + // reset time and block delays to 0, malleate may change to a specific non-zero value. + delayTimePeriod = 0 + delayBlockPeriod = 0 + tc.malleate() // make changes as necessary + + ctx := suite.chainA.GetContext() + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, path.EndpointA.ClientID) + + err = clientState.VerifyPacketAcknowledgement( + ctx, store, suite.chainA.Codec, proofHeight, delayTimePeriod, delayBlockPeriod, &prefix, proof, + packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), ibcmock.MockAcknowledgement.Acknowledgement(), + ) + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} + +// test verification of the absent acknowledgement on chainB being represented +// in the light client on chainA. A send from chainB to chainA is simulated, but +// no receive. +func (suite *DymintTestSuite) TestVerifyPacketReceiptAbsence() { + var ( + clientState *types.ClientState + proof []byte + delayTimePeriod uint64 + delayBlockPeriod uint64 + proofHeight exported.Height + prefix commitmenttypes.MerklePrefix + ) + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "successful verification", func() {}, true, + }, + { + name: "delay time period has passed", + malleate: func() { + delayTimePeriod = uint64(time.Second.Nanoseconds()) + }, + expPass: true, + }, + { + name: "delay time period has not passed", + malleate: func() { + delayTimePeriod = uint64(time.Hour.Nanoseconds()) + }, + expPass: false, + }, + { + name: "delay block period has passed", + malleate: func() { + delayBlockPeriod = 1 + }, + expPass: true, + }, + { + name: "delay block period has not passed", + malleate: func() { + delayBlockPeriod = 10 + }, + expPass: false, + }, + + { + "ApplyPrefix failed", func() { + prefix = commitmenttypes.MerklePrefix{} + }, false, + }, + { + "latest client height < height", func() { + proofHeight = clientState.LatestHeight.Increment() + }, false, + }, + { + "proof verification failed", func() { + proof = invalidProof + }, false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + // setup testing conditions + path := ibctesting.NewPath(suite.chainA, suite.chainB) + suite.coordinator.Setup(path) + packet := channeltypes.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.NewHeight(0, 100), 0) + + // send packet, but no recv + err := path.EndpointA.SendPacket(packet) + suite.Require().NoError(err) + + var ok bool + clientStateI := suite.chainA.GetClientState(path.EndpointA.ClientID) + clientState, ok = clientStateI.(*types.ClientState) + suite.Require().True(ok) + + prefix = suite.chainB.GetPrefix() + + // make packet receipt absence proof + receiptKey := host.PacketReceiptKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + proof, proofHeight = path.EndpointB.QueryProof(receiptKey) + + // reset time and block delays to 0, malleate may change to a specific non-zero value. + delayTimePeriod = 0 + delayBlockPeriod = 0 + tc.malleate() // make changes as necessary + + ctx := suite.chainA.GetContext() + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, path.EndpointA.ClientID) + + err = clientState.VerifyPacketReceiptAbsence( + ctx, store, suite.chainA.Codec, proofHeight, delayTimePeriod, delayBlockPeriod, &prefix, proof, + packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), + ) + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} + +// test verification of the next receive sequence on chainB being represented +// in the light client on chainA. A send and receive from chainB to chainA is +// simulated. +func (suite *DymintTestSuite) TestVerifyNextSeqRecv() { + var ( + clientState *types.ClientState + proof []byte + delayTimePeriod uint64 + delayBlockPeriod uint64 + proofHeight exported.Height + prefix commitmenttypes.MerklePrefix + ) + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "successful verification", func() {}, true, + }, + { + name: "delay time period has passed", + malleate: func() { + delayTimePeriod = uint64(time.Second.Nanoseconds()) + }, + expPass: true, + }, + { + name: "delay time period has not passed", + malleate: func() { + delayTimePeriod = uint64(time.Hour.Nanoseconds()) + }, + expPass: false, + }, + { + name: "delay block period has passed", + malleate: func() { + delayBlockPeriod = 1 + }, + expPass: true, + }, + { + name: "delay block period has not passed", + malleate: func() { + delayBlockPeriod = 10 + }, + expPass: false, + }, + + { + "ApplyPrefix failed", func() { + prefix = commitmenttypes.MerklePrefix{} + }, false, + }, + { + "latest client height < height", func() { + proofHeight = clientState.LatestHeight.Increment() + }, false, + }, + { + "proof verification failed", func() { + proof = invalidProof + }, false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + // setup testing conditions + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetChannelOrdered() + suite.coordinator.Setup(path) + packet := channeltypes.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.NewHeight(0, 100), 0) + + // send packet + err := path.EndpointA.SendPacket(packet) + suite.Require().NoError(err) + + // next seq recv incremented + err = path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + + var ok bool + clientStateI := suite.chainA.GetClientState(path.EndpointA.ClientID) + clientState, ok = clientStateI.(*types.ClientState) + suite.Require().True(ok) + + prefix = suite.chainB.GetPrefix() + + // make next seq recv proof + nextSeqRecvKey := host.NextSequenceRecvKey(packet.GetDestPort(), packet.GetDestChannel()) + proof, proofHeight = suite.chainB.QueryProof(nextSeqRecvKey) + + // reset time and block delays to 0, malleate may change to a specific non-zero value. + delayTimePeriod = 0 + delayBlockPeriod = 0 + tc.malleate() // make changes as necessary + + ctx := suite.chainA.GetContext() + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, path.EndpointA.ClientID) + + err = clientState.VerifyNextSequenceRecv( + ctx, store, suite.chainA.Codec, proofHeight, delayTimePeriod, delayBlockPeriod, &prefix, proof, + packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()+1, + ) + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} diff --git a/modules/light-clients/01-dymint/types/codec.go b/modules/light-clients/01-dymint/types/codec.go new file mode 100644 index 00000000000..20787ce0ed0 --- /dev/null +++ b/modules/light-clients/01-dymint/types/codec.go @@ -0,0 +1,28 @@ +package types + +import ( + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + + "github.com/cosmos/ibc-go/v3/modules/core/exported" +) + +// RegisterInterfaces registers the dymint 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.Header)(nil), + &Header{}, + ) + registry.RegisterImplementations( + (*exported.Misbehaviour)(nil), + &Misbehaviour{}, + ) +} diff --git a/modules/light-clients/01-dymint/types/consensus_state.go b/modules/light-clients/01-dymint/types/consensus_state.go new file mode 100644 index 00000000000..87324cb0ecb --- /dev/null +++ b/modules/light-clients/01-dymint/types/consensus_state.go @@ -0,0 +1,58 @@ +package types + +import ( + "time" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + tmbytes "github.com/tendermint/tendermint/libs/bytes" + tmtypes "github.com/tendermint/tendermint/types" + + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" +) + +// 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 tmbytes.HexBytes, +) *ConsensusState { + return &ConsensusState{ + Timestamp: timestamp, + Root: root, + NextValidatorsHash: nextValsHash, + } +} + +// ClientType returns Dymint +func (ConsensusState) ClientType() string { + return exported.Dymint +} + +// 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 dymint 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 sdkerrors.Wrap(clienttypes.ErrInvalidConsensus, "root cannot be empty") + } + if err := tmtypes.ValidateHash(cs.NextValidatorsHash); err != nil { + return sdkerrors.Wrap(err, "next validators hash is invalid") + } + if cs.Timestamp.Unix() <= 0 { + return sdkerrors.Wrap(clienttypes.ErrInvalidConsensus, "timestamp must be a positive Unix time") + } + return nil +} diff --git a/modules/light-clients/01-dymint/types/consensus_state_test.go b/modules/light-clients/01-dymint/types/consensus_state_test.go new file mode 100644 index 00000000000..b9b020dd229 --- /dev/null +++ b/modules/light-clients/01-dymint/types/consensus_state_test.go @@ -0,0 +1,76 @@ +package types_test + +import ( + "time" + + commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" + "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" +) + +func (suite *DymintTestSuite) TestConsensusStateValidateBasic() { + testCases := []struct { + msg string + consensusState *types.ConsensusState + expectPass bool + }{ + {"success", + &types.ConsensusState{ + Timestamp: suite.now, + Root: commitmenttypes.NewMerkleRoot([]byte("app_hash")), + NextValidatorsHash: suite.valsHash, + }, + true}, + {"success with sentinel", + &types.ConsensusState{ + Timestamp: suite.now, + Root: commitmenttypes.NewMerkleRoot([]byte(types.SentinelRoot)), + NextValidatorsHash: suite.valsHash, + }, + true}, + {"root is nil", + &types.ConsensusState{ + Timestamp: suite.now, + Root: commitmenttypes.MerkleRoot{}, + NextValidatorsHash: suite.valsHash, + }, + false}, + {"root is empty", + &types.ConsensusState{ + Timestamp: suite.now, + Root: commitmenttypes.MerkleRoot{}, + NextValidatorsHash: suite.valsHash, + }, + false}, + {"nextvalshash is invalid", + &types.ConsensusState{ + Timestamp: suite.now, + Root: commitmenttypes.NewMerkleRoot([]byte("app_hash")), + NextValidatorsHash: []byte("hi"), + }, + false}, + + {"timestamp is zero", + &types.ConsensusState{ + Timestamp: time.Time{}, + Root: commitmenttypes.NewMerkleRoot([]byte("app_hash")), + NextValidatorsHash: suite.valsHash, + }, + false}, + } + + for i, tc := range testCases { + tc := tc + + // check just to increase coverage + suite.Require().Equal(exported.Dymint, 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/01-dymint/types/dymint.pb.go b/modules/light-clients/01-dymint/types/dymint.pb.go new file mode 100644 index 00000000000..8b55960e90e --- /dev/null +++ b/modules/light-clients/01-dymint/types/dymint.pb.go @@ -0,0 +1,1917 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/lightclients/dymint/dymint.proto + +package types + +import ( + fmt "fmt" + _go "github.com/confio/ics23/go" + types "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + types1 "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + github_com_gogo_protobuf_types "github.com/gogo/protobuf/types" + github_com_tendermint_tendermint_libs_bytes "github.com/tendermint/tendermint/libs/bytes" + types2 "github.com/tendermint/tendermint/proto/tendermint/types" + _ "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 Dymint 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" yaml:"trust_level"` + // duration of the period since the LastestTimestamp 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" yaml:"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" yaml:"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" yaml:"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" yaml:"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" yaml:"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" yaml:"proof_specs"` + // 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" yaml:"upgrade_path"` + // This flag, when set to true, will allow governance to recover a client + // which has expired + AllowUpdateAfterExpiry bool `protobuf:"varint,10,opt,name=allow_update_after_expiry,json=allowUpdateAfterExpiry,proto3" json:"allow_update_after_expiry,omitempty" yaml:"allow_update_after_expiry"` + // This flag, when set to true, will allow governance to unfreeze a client + // whose chain has experienced a misbehaviour event + AllowUpdateAfterMisbehaviour bool `protobuf:"varint,11,opt,name=allow_update_after_misbehaviour,json=allowUpdateAfterMisbehaviour,proto3" json:"allow_update_after_misbehaviour,omitempty" yaml:"allow_update_after_misbehaviour"` +} + +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_cef6cb256dd4d990, []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 Dymint. +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_tendermint_tendermint_libs_bytes.HexBytes `protobuf:"bytes,3,opt,name=next_validators_hash,json=nextValidatorsHash,proto3,casttype=github.com/tendermint/tendermint/libs/bytes.HexBytes" json:"next_validators_hash,omitempty" yaml:"next_validators_hash"` +} + +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_cef6cb256dd4d990, []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 string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty" yaml:"client_id"` + Header1 *Header `protobuf:"bytes,2,opt,name=header_1,json=header1,proto3" json:"header_1,omitempty" yaml:"header_1"` + Header2 *Header `protobuf:"bytes,3,opt,name=header_2,json=header2,proto3" json:"header_2,omitempty" yaml:"header_2"` +} + +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_cef6cb256dd4d990, []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 Dymint client consensus Header. +// It encapsulates all the information necessary to update from a trusted +// Dymint 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" yaml:"signed_header"` + ValidatorSet *types2.ValidatorSet `protobuf:"bytes,2,opt,name=validator_set,json=validatorSet,proto3" json:"validator_set,omitempty" yaml:"validator_set"` + TrustedHeight types.Height `protobuf:"bytes,3,opt,name=trusted_height,json=trustedHeight,proto3" json:"trusted_height" yaml:"trusted_height"` + TrustedValidators *types2.ValidatorSet `protobuf:"bytes,4,opt,name=trusted_validators,json=trustedValidators,proto3" json:"trusted_validators,omitempty" yaml:"trusted_validators"` +} + +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_cef6cb256dd4d990, []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_cef6cb256dd4d990, []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.dymint.v1.ClientState") + proto.RegisterType((*ConsensusState)(nil), "ibc.lightclients.dymint.v1.ConsensusState") + proto.RegisterType((*Misbehaviour)(nil), "ibc.lightclients.dymint.v1.Misbehaviour") + proto.RegisterType((*Header)(nil), "ibc.lightclients.dymint.v1.Header") + proto.RegisterType((*Fraction)(nil), "ibc.lightclients.dymint.v1.Fraction") +} + +func init() { + proto.RegisterFile("ibc/lightclients/dymint/dymint.proto", fileDescriptor_cef6cb256dd4d990) +} + +var fileDescriptor_cef6cb256dd4d990 = []byte{ + // 1083 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x56, 0xcf, 0x6f, 0xe3, 0xc4, + 0x17, 0x6f, 0xda, 0x7e, 0xb7, 0xc9, 0x24, 0xdd, 0xee, 0xd7, 0x94, 0x6e, 0x5a, 0xba, 0x71, 0x34, + 0x54, 0x50, 0x21, 0xd5, 0x26, 0x29, 0xa7, 0x8a, 0xcb, 0xba, 0x0b, 0x6a, 0x11, 0x2b, 0x55, 0x2e, + 0x3f, 0x24, 0xd0, 0xca, 0x4c, 0xec, 0x49, 0x3c, 0x5a, 0xdb, 0x63, 0x3c, 0xe3, 0xd0, 0x72, 0x47, + 0x82, 0xdb, 0x1e, 0x11, 0x27, 0x0e, 0xfc, 0x31, 0x7b, 0xec, 0x91, 0x93, 0x41, 0x2d, 0x7f, 0x41, + 0x8e, 0x9c, 0x90, 0x67, 0xc6, 0x3f, 0xd2, 0x6d, 0x55, 0xf5, 0xd2, 0xce, 0x7b, 0xef, 0xf3, 0x3e, + 0x9f, 0xcc, 0x9b, 0x37, 0x6f, 0x0c, 0x76, 0xc8, 0xc8, 0x35, 0x03, 0x32, 0xf1, 0xb9, 0x1b, 0x10, + 0x1c, 0x71, 0x66, 0x7a, 0xe7, 0x21, 0x89, 0xb8, 0xfa, 0x67, 0xc4, 0x09, 0xe5, 0x54, 0xdb, 0x22, + 0x23, 0xd7, 0xa8, 0xa3, 0x0c, 0x15, 0x9e, 0x0e, 0xb6, 0xfa, 0x1c, 0x47, 0x1e, 0x4e, 0x44, 0x12, + 0x3f, 0x8f, 0x31, 0x33, 0xa7, 0x28, 0x20, 0x1e, 0xe2, 0x34, 0x91, 0xd9, 0x5b, 0xdb, 0x6f, 0x20, + 0xc4, 0x5f, 0x15, 0xed, 0xc4, 0x09, 0xa5, 0xe3, 0xc2, 0xea, 0x4d, 0x28, 0x9d, 0x04, 0xd8, 0x14, + 0xd6, 0x28, 0x1d, 0x9b, 0x5e, 0x9a, 0x20, 0x4e, 0x68, 0xa4, 0xe2, 0xfa, 0xf5, 0x38, 0x27, 0x21, + 0x66, 0x1c, 0x85, 0x71, 0x01, 0xc8, 0x37, 0xe4, 0xd2, 0x04, 0x9b, 0xf2, 0xa7, 0x9a, 0xd3, 0x81, + 0x5a, 0x29, 0xc0, 0xfb, 0x15, 0x80, 0x86, 0x21, 0xe1, 0x61, 0x01, 0x2a, 0x2d, 0x05, 0x5c, 0x9f, + 0xd0, 0x09, 0x15, 0x4b, 0x33, 0x5f, 0x49, 0x2f, 0xfc, 0x67, 0x05, 0xb4, 0x0f, 0x05, 0xdf, 0x29, + 0x47, 0x1c, 0x6b, 0x9b, 0xa0, 0xe9, 0xfa, 0x88, 0x44, 0x0e, 0xf1, 0xba, 0x8d, 0x7e, 0x63, 0xb7, + 0x65, 0xaf, 0x08, 0xfb, 0xd8, 0xd3, 0x10, 0x68, 0xf3, 0x24, 0x65, 0xdc, 0x09, 0xf0, 0x14, 0x07, + 0xdd, 0xc5, 0x7e, 0x63, 0xb7, 0x3d, 0xdc, 0x31, 0x6e, 0xaf, 0xa5, 0xf1, 0x69, 0x82, 0xdc, 0x7c, + 0xb3, 0xd6, 0xd6, 0xeb, 0x4c, 0x5f, 0x98, 0x65, 0xba, 0x76, 0x8e, 0xc2, 0xe0, 0x00, 0xd6, 0x68, + 0xa0, 0x0d, 0x84, 0xf5, 0x79, 0x6e, 0x68, 0x63, 0xb0, 0x26, 0x2c, 0x12, 0x4d, 0x9c, 0x18, 0x27, + 0x84, 0x7a, 0xdd, 0x25, 0x21, 0xb3, 0x69, 0xc8, 0x42, 0x19, 0x45, 0xa1, 0x8c, 0x67, 0xaa, 0x90, + 0x16, 0x54, 0xdc, 0x1b, 0x35, 0xee, 0x2a, 0x1f, 0xfe, 0xfa, 0x97, 0xde, 0xb0, 0x1f, 0x16, 0xde, + 0x13, 0xe1, 0xd4, 0x08, 0x78, 0x94, 0x46, 0x23, 0x1a, 0x79, 0x35, 0xa1, 0xe5, 0xbb, 0x84, 0xde, + 0x55, 0x42, 0x8f, 0xa5, 0xd0, 0x75, 0x02, 0xa9, 0xb4, 0x56, 0xba, 0x95, 0x14, 0x06, 0x6b, 0x21, + 0x3a, 0x73, 0xdc, 0x80, 0xba, 0x2f, 0x1d, 0x2f, 0x21, 0x63, 0xde, 0xfd, 0xdf, 0x3d, 0xb7, 0x74, + 0x2d, 0x5f, 0x0a, 0xad, 0x86, 0xe8, 0xec, 0x30, 0x77, 0x3e, 0xcb, 0x7d, 0xda, 0x0b, 0xb0, 0x3a, + 0x4e, 0xe8, 0x8f, 0x38, 0x72, 0x7c, 0x9c, 0x1f, 0x46, 0xf7, 0x81, 0x10, 0xd9, 0x12, 0xc7, 0x93, + 0xb7, 0x87, 0xa1, 0xba, 0x66, 0x3a, 0x30, 0x8e, 0x04, 0xc2, 0xda, 0x56, 0x2a, 0xeb, 0x52, 0x65, + 0x2e, 0x1d, 0xda, 0x1d, 0x69, 0x4b, 0x6c, 0x4e, 0x1f, 0x20, 0x8e, 0x19, 0x2f, 0xe8, 0x57, 0xee, + 0x4b, 0x3f, 0x97, 0x0e, 0xed, 0x8e, 0xb4, 0x15, 0xfd, 0x31, 0x68, 0x8b, 0x6b, 0xe3, 0xb0, 0x18, + 0xbb, 0xac, 0xdb, 0xec, 0x2f, 0xed, 0xb6, 0x87, 0x8f, 0x0c, 0xe2, 0xb2, 0xe1, 0xbe, 0x71, 0x92, + 0x47, 0x4e, 0x63, 0xec, 0x5a, 0x1b, 0x55, 0x0b, 0xd5, 0xe0, 0xd0, 0x06, 0x71, 0x01, 0x61, 0xda, + 0x01, 0xe8, 0xa4, 0xf1, 0x24, 0x41, 0x1e, 0x76, 0x62, 0xc4, 0xfd, 0x6e, 0xab, 0xbf, 0xb4, 0xdb, + 0xb2, 0x1e, 0xcf, 0x32, 0xfd, 0x2d, 0x75, 0x6e, 0xb5, 0x28, 0xb4, 0xdb, 0xca, 0x3c, 0x41, 0xdc, + 0xd7, 0x1c, 0xb0, 0x89, 0x82, 0x80, 0xfe, 0xe0, 0xa4, 0xb1, 0x87, 0x38, 0x76, 0xd0, 0x98, 0xe3, + 0xc4, 0xc1, 0x67, 0x31, 0x49, 0xce, 0xbb, 0xa0, 0xdf, 0xd8, 0x6d, 0x5a, 0x3b, 0xb3, 0x4c, 0xef, + 0x4b, 0xa2, 0x5b, 0xa1, 0xd0, 0xde, 0x10, 0xb1, 0x2f, 0x45, 0xe8, 0x69, 0x1e, 0xf9, 0x44, 0x04, + 0xb4, 0xef, 0x81, 0x7e, 0x43, 0x56, 0x48, 0xd8, 0x08, 0xfb, 0x68, 0x4a, 0x68, 0x9a, 0x74, 0xdb, + 0x42, 0xe6, 0x83, 0x59, 0xa6, 0xbf, 0x77, 0xab, 0x4c, 0x3d, 0x01, 0xda, 0xdb, 0xd7, 0xc5, 0x9e, + 0xd7, 0xc2, 0x07, 0xcb, 0x3f, 0xff, 0xae, 0x2f, 0xc0, 0x3f, 0x16, 0xc1, 0xc3, 0x43, 0x1a, 0x31, + 0x1c, 0xb1, 0x94, 0xc9, 0x9b, 0x6e, 0x81, 0x56, 0x39, 0x6c, 0xc4, 0x55, 0xcf, 0x8f, 0xf3, 0x7a, + 0x4b, 0x7e, 0x51, 0x20, 0xac, 0x66, 0x7e, 0x9c, 0xaf, 0xf2, 0xce, 0xab, 0xd2, 0xb4, 0x8f, 0xc1, + 0x72, 0x42, 0x29, 0x57, 0xb3, 0x00, 0xd6, 0xba, 0xa1, 0x9a, 0x3e, 0xd3, 0x81, 0xf1, 0x1c, 0x27, + 0x2f, 0x03, 0x6c, 0x53, 0xca, 0xad, 0xe5, 0x9c, 0xc6, 0x16, 0x59, 0xda, 0x2f, 0x0d, 0xb0, 0x1e, + 0xe1, 0x33, 0xee, 0x94, 0x13, 0x96, 0x39, 0x3e, 0x62, 0xbe, 0xb8, 0xf3, 0x1d, 0xeb, 0xeb, 0x59, + 0xa6, 0xbf, 0x23, 0x6b, 0x70, 0x13, 0x0a, 0xfe, 0x9b, 0xe9, 0x1f, 0x4d, 0x08, 0xf7, 0xd3, 0x51, + 0x2e, 0x67, 0xd6, 0xa7, 0x72, 0xb5, 0x0c, 0xc8, 0x88, 0x99, 0xa3, 0x73, 0x8e, 0x99, 0x71, 0x84, + 0xcf, 0xac, 0x7c, 0x61, 0x6b, 0x39, 0xdd, 0x57, 0x25, 0xdb, 0x11, 0x62, 0xbe, 0x2a, 0xd3, 0x4f, + 0x8b, 0xa0, 0x53, 0xaf, 0x9e, 0x36, 0x00, 0x2d, 0xd9, 0xd8, 0xe5, 0x3c, 0xb4, 0xd6, 0x67, 0x99, + 0xfe, 0x48, 0xfe, 0xac, 0x32, 0x04, 0xed, 0xa6, 0x5c, 0x1f, 0x7b, 0xda, 0x0b, 0xd0, 0xf4, 0x31, + 0xf2, 0x70, 0xe2, 0x0c, 0xe6, 0xea, 0x72, 0xcb, 0x8c, 0x3c, 0x12, 0x58, 0xab, 0x77, 0x99, 0xe9, + 0x2b, 0x72, 0x3d, 0x98, 0x65, 0xfa, 0x9a, 0x14, 0x28, 0x88, 0xa0, 0xbd, 0x22, 0x97, 0x83, 0x1a, + 0xfd, 0x50, 0xcd, 0xc6, 0x7b, 0xd2, 0x0f, 0xdf, 0xa0, 0x1f, 0x96, 0xf4, 0x43, 0x55, 0x87, 0xdf, + 0x96, 0xc0, 0x03, 0x89, 0xd6, 0x10, 0x58, 0x65, 0x64, 0x12, 0x61, 0xcf, 0x91, 0x10, 0xd5, 0x2a, + 0x3d, 0xa3, 0x2a, 0xb2, 0x21, 0xdf, 0xbf, 0x53, 0x01, 0x53, 0x82, 0xdb, 0x17, 0x99, 0xde, 0xa8, + 0x6e, 0xff, 0x1c, 0x05, 0xb4, 0x3b, 0xac, 0x86, 0xcd, 0x87, 0x4b, 0x79, 0xb6, 0x0e, 0xc3, 0x45, + 0x3b, 0xdd, 0x20, 0x51, 0x1e, 0xda, 0x29, 0xe6, 0x56, 0xb7, 0xa2, 0x9f, 0x4b, 0x87, 0x76, 0x67, + 0x5a, 0xc3, 0x69, 0xdf, 0x01, 0x39, 0xfe, 0x85, 0xbe, 0x18, 0x5e, 0x4b, 0x77, 0x0e, 0xaf, 0x27, + 0x6a, 0x78, 0xbd, 0x5d, 0x7b, 0x54, 0xca, 0x7c, 0x68, 0xaf, 0x2a, 0x87, 0x1a, 0x5f, 0x01, 0xd0, + 0x0a, 0x44, 0xd5, 0xa4, 0xea, 0x41, 0xb9, 0x6b, 0x17, 0x4f, 0x66, 0x99, 0xbe, 0x39, 0xaf, 0x52, + 0x71, 0x40, 0xfb, 0xff, 0xca, 0x59, 0xb5, 0x2b, 0xfc, 0x0c, 0x34, 0x8b, 0x87, 0x55, 0xdb, 0x06, + 0xad, 0x28, 0x0d, 0x71, 0x92, 0x47, 0xc4, 0xc9, 0x2c, 0xdb, 0x95, 0x43, 0xeb, 0x83, 0xb6, 0x87, + 0x23, 0x1a, 0x92, 0x48, 0xc4, 0x17, 0x45, 0xbc, 0xee, 0xb2, 0xbe, 0x7d, 0x7d, 0xd9, 0x6b, 0x5c, + 0x5c, 0xf6, 0x1a, 0x7f, 0x5f, 0xf6, 0x1a, 0xaf, 0xae, 0x7a, 0x0b, 0x17, 0x57, 0xbd, 0x85, 0x3f, + 0xaf, 0x7a, 0x0b, 0xdf, 0x3c, 0xad, 0x5d, 0x2d, 0x97, 0xb2, 0x90, 0x32, 0x93, 0x8c, 0xdc, 0xbd, + 0x09, 0x35, 0xa7, 0xfb, 0x66, 0x48, 0xbd, 0x34, 0xc0, 0x4c, 0x7e, 0x69, 0xed, 0x15, 0x9f, 0x5a, + 0x1f, 0x0e, 0xf6, 0xd4, 0xd7, 0x96, 0xd8, 0xe7, 0xe8, 0x81, 0x18, 0x23, 0xfb, 0xff, 0x05, 0x00, + 0x00, 0xff, 0xff, 0x0d, 0x0a, 0x5e, 0x40, 0x95, 0x09, 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 = encodeVarintDymint(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 = encodeVarintDymint(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x42 + } + } + { + size, err := m.LatestHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintDymint(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a + { + size, err := m.FrozenHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintDymint(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + n3, err3 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.MaxClockDrift, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.MaxClockDrift):]) + if err3 != nil { + return 0, err3 + } + i -= n3 + i = encodeVarintDymint(dAtA, i, uint64(n3)) + i-- + dAtA[i] = 0x2a + n4, err4 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.UnbondingPeriod, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.UnbondingPeriod):]) + if err4 != nil { + return 0, err4 + } + i -= n4 + i = encodeVarintDymint(dAtA, i, uint64(n4)) + i-- + dAtA[i] = 0x22 + n5, err5 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.TrustingPeriod, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.TrustingPeriod):]) + if err5 != nil { + return 0, err5 + } + i -= n5 + i = encodeVarintDymint(dAtA, i, uint64(n5)) + i-- + dAtA[i] = 0x1a + { + size, err := m.TrustLevel.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintDymint(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.ChainId) > 0 { + i -= len(m.ChainId) + copy(dAtA[i:], m.ChainId) + i = encodeVarintDymint(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 = encodeVarintDymint(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 = encodeVarintDymint(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + n8, err8 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Timestamp, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Timestamp):]) + if err8 != nil { + return 0, err8 + } + i -= n8 + i = encodeVarintDymint(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 = encodeVarintDymint(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 = encodeVarintDymint(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintDymint(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 = encodeVarintDymint(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + { + size, err := m.TrustedHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintDymint(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 = encodeVarintDymint(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 = encodeVarintDymint(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 = encodeVarintDymint(dAtA, i, uint64(m.Denominator)) + i-- + dAtA[i] = 0x10 + } + if m.Numerator != 0 { + i = encodeVarintDymint(dAtA, i, uint64(m.Numerator)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintDymint(dAtA []byte, offset int, v uint64) int { + offset -= sovDymint(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 + sovDymint(uint64(l)) + } + l = m.TrustLevel.Size() + n += 1 + l + sovDymint(uint64(l)) + l = github_com_gogo_protobuf_types.SizeOfStdDuration(m.TrustingPeriod) + n += 1 + l + sovDymint(uint64(l)) + l = github_com_gogo_protobuf_types.SizeOfStdDuration(m.UnbondingPeriod) + n += 1 + l + sovDymint(uint64(l)) + l = github_com_gogo_protobuf_types.SizeOfStdDuration(m.MaxClockDrift) + n += 1 + l + sovDymint(uint64(l)) + l = m.FrozenHeight.Size() + n += 1 + l + sovDymint(uint64(l)) + l = m.LatestHeight.Size() + n += 1 + l + sovDymint(uint64(l)) + if len(m.ProofSpecs) > 0 { + for _, e := range m.ProofSpecs { + l = e.Size() + n += 1 + l + sovDymint(uint64(l)) + } + } + if len(m.UpgradePath) > 0 { + for _, s := range m.UpgradePath { + l = len(s) + n += 1 + l + sovDymint(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_gogo_protobuf_types.SizeOfStdTime(m.Timestamp) + n += 1 + l + sovDymint(uint64(l)) + l = m.Root.Size() + n += 1 + l + sovDymint(uint64(l)) + l = len(m.NextValidatorsHash) + if l > 0 { + n += 1 + l + sovDymint(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 + sovDymint(uint64(l)) + } + if m.Header1 != nil { + l = m.Header1.Size() + n += 1 + l + sovDymint(uint64(l)) + } + if m.Header2 != nil { + l = m.Header2.Size() + n += 1 + l + sovDymint(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 + sovDymint(uint64(l)) + } + if m.ValidatorSet != nil { + l = m.ValidatorSet.Size() + n += 1 + l + sovDymint(uint64(l)) + } + l = m.TrustedHeight.Size() + n += 1 + l + sovDymint(uint64(l)) + if m.TrustedValidators != nil { + l = m.TrustedValidators.Size() + n += 1 + l + sovDymint(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 + sovDymint(uint64(m.Numerator)) + } + if m.Denominator != 0 { + n += 1 + sovDymint(uint64(m.Denominator)) + } + return n +} + +func sovDymint(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozDymint(x uint64) (n int) { + return sovDymint(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 ErrIntOverflowDymint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + 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 ErrIntOverflowDymint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDymint + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthDymint + } + 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 ErrIntOverflowDymint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDymint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDymint + } + 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 ErrIntOverflowDymint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDymint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDymint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_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 ErrIntOverflowDymint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDymint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDymint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_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 ErrIntOverflowDymint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDymint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDymint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_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 ErrIntOverflowDymint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDymint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDymint + } + 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 ErrIntOverflowDymint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDymint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDymint + } + 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 ErrIntOverflowDymint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDymint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDymint + } + 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 ErrIntOverflowDymint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDymint + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthDymint + } + 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 ErrIntOverflowDymint + } + 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 ErrIntOverflowDymint + } + 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 := skipDymint(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthDymint + } + 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 ErrIntOverflowDymint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + 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 ErrIntOverflowDymint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDymint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDymint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_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 ErrIntOverflowDymint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDymint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDymint + } + 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 ErrIntOverflowDymint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDymint + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthDymint + } + 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 := skipDymint(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthDymint + } + 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 ErrIntOverflowDymint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + 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 ErrIntOverflowDymint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDymint + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthDymint + } + 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 ErrIntOverflowDymint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDymint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDymint + } + 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 ErrIntOverflowDymint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDymint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDymint + } + 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 := skipDymint(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthDymint + } + 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 ErrIntOverflowDymint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + 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 ErrIntOverflowDymint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDymint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDymint + } + 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 ErrIntOverflowDymint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDymint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDymint + } + 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 ErrIntOverflowDymint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDymint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDymint + } + 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 ErrIntOverflowDymint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDymint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDymint + } + 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 := skipDymint(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthDymint + } + 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 ErrIntOverflowDymint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + 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 ErrIntOverflowDymint + } + 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 ErrIntOverflowDymint + } + 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 := skipDymint(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthDymint + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipDymint(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, ErrIntOverflowDymint + } + 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, ErrIntOverflowDymint + } + 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, ErrIntOverflowDymint + } + 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, ErrInvalidLengthDymint + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupDymint + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthDymint + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthDymint = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowDymint = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupDymint = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/light-clients/01-dymint/types/dymint_test.go b/modules/light-clients/01-dymint/types/dymint_test.go new file mode 100644 index 00000000000..b17263b3ef2 --- /dev/null +++ b/modules/light-clients/01-dymint/types/dymint_test.go @@ -0,0 +1,110 @@ +package types_test + +import ( + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/suite" + tmbytes "github.com/tendermint/tendermint/libs/bytes" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + tmtypes "github.com/tendermint/tendermint/types" + + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" + ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" + ibctesting "github.com/cosmos/ibc-go/v3/testing" + ibctestingmock "github.com/cosmos/ibc-go/v3/testing/mock" + "github.com/cosmos/ibc-go/v3/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"} +) + +type DymintTestSuite struct { + suite.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 tmtypes.PrivValidator + valSet *tmtypes.ValidatorSet + valsHash tmbytes.HexBytes + header *ibctmtypes.Header + now time.Time + headerTime time.Time + clientTime time.Time +} + +func (suite *DymintTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinatorWithConsensusType(suite.T(), []string{exported.Dymint, exported.Dymint}) + 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(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 = ibctestingmock.NewPV() + + pubKey, err := suite.privVal.GetPubKey() + suite.Require().NoError(err) + + heightMinus1 := clienttypes.NewHeight(0, height.RevisionHeight-1) + + val := tmtypes.NewValidator(pubKey, 10) + suite.valSet = tmtypes.NewValidatorSet([]*tmtypes.Validator{val}) + suite.valsHash = suite.valSet.Hash() + chainADymint := suite.chainA.TestChainClient.(*ibctesting.TestChainDymint) + suite.header = chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, []tmtypes.PrivValidator{suite.privVal}) + suite.ctx = app.BaseApp.NewContext(checkTx, tmproto.Header{Height: 1, Time: suite.now}) +} + +func getSuiteSigners(suite *DymintTestSuite) []tmtypes.PrivValidator { + return []tmtypes.PrivValidator{suite.privVal} +} + +func getBothSigners(suite *DymintTestSuite, altVal *tmtypes.Validator, altPrivVal tmtypes.PrivValidator) (*tmtypes.ValidatorSet, []tmtypes.PrivValidator) { + // Create bothValSet with both suite validator and altVal. Would be valid update + bothValSet := tmtypes.NewValidatorSet(append(suite.valSet.Validators, altVal)) + // Create signer array and ensure it is in same order as bothValSet + _, suiteVal := suite.valSet.GetByIndex(0) + bothSigners := ibctesting.CreateSortedSignerArray(altPrivVal, suite.privVal, altVal, suiteVal) + return bothValSet, bothSigners +} + +func TestDymintTestSuite(t *testing.T) { + suite.Run(t, new(DymintTestSuite)) +} diff --git a/modules/light-clients/01-dymint/types/errors.go b/modules/light-clients/01-dymint/types/errors.go new file mode 100644 index 00000000000..6e9dd7dc6bd --- /dev/null +++ b/modules/light-clients/01-dymint/types/errors.go @@ -0,0 +1,26 @@ +package types + +import ( + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +const ( + SubModuleName = "dymint-client" +) + +// IBC dymint client sentinel errors +var ( + ErrInvalidChainID = sdkerrors.Register(SubModuleName, 2, "invalid chain-id") + ErrInvalidTrustingPeriod = sdkerrors.Register(SubModuleName, 3, "invalid trusting period") + ErrInvalidUnbondingPeriod = sdkerrors.Register(SubModuleName, 4, "invalid unbonding period") + ErrInvalidHeaderHeight = sdkerrors.Register(SubModuleName, 5, "invalid header height") + ErrInvalidHeader = sdkerrors.Register(SubModuleName, 6, "invalid header") + ErrInvalidMaxClockDrift = sdkerrors.Register(SubModuleName, 7, "invalid max clock drift") + ErrProcessedTimeNotFound = sdkerrors.Register(SubModuleName, 8, "processed time not found") + ErrProcessedHeightNotFound = sdkerrors.Register(SubModuleName, 9, "processed height not found") + ErrDelayPeriodNotPassed = sdkerrors.Register(SubModuleName, 10, "packet-specified delay period has not been reached") + ErrTrustingPeriodExpired = sdkerrors.Register(SubModuleName, 11, "time since latest trusted state has passed the trusting period") + ErrUnbondingPeriodExpired = sdkerrors.Register(SubModuleName, 12, "time since latest trusted state has passed the unbonding period") + ErrInvalidProofSpecs = sdkerrors.Register(SubModuleName, 13, "invalid proof specs") + ErrInvalidValidatorSet = sdkerrors.Register(SubModuleName, 14, "invalid validator set") +) diff --git a/modules/light-clients/01-dymint/types/fraction.go b/modules/light-clients/01-dymint/types/fraction.go new file mode 100644 index 00000000000..1b4aa7606eb --- /dev/null +++ b/modules/light-clients/01-dymint/types/fraction.go @@ -0,0 +1,25 @@ +package types + +import ( + tmmath "github.com/tendermint/tendermint/libs/math" + "github.com/tendermint/tendermint/light" +) + +// DefaultTrustLevel is the dymint light client default trust level +var DefaultTrustLevel = NewFractionFromTm(light.DefaultTrustLevel) + +// NewFractionFromTm returns a new Fraction instance from a tmmath.Fraction +func NewFractionFromTm(f tmmath.Fraction) Fraction { + return Fraction{ + Numerator: f.Numerator, + Denominator: f.Denominator, + } +} + +// ToDymint converts Fraction to tmmath.Fraction +func (f Fraction) ToDymint() tmmath.Fraction { + return tmmath.Fraction{ + Numerator: f.Numerator, + Denominator: f.Denominator, + } +} diff --git a/modules/light-clients/01-dymint/types/genesis.go b/modules/light-clients/01-dymint/types/genesis.go new file mode 100644 index 00000000000..82a996d3696 --- /dev/null +++ b/modules/light-clients/01-dymint/types/genesis.go @@ -0,0 +1,22 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" +) + +// ExportMetadata exports all the consensus metadata in the client store so they can be included in clients genesis +// and imported by a ClientKeeper +func (cs ClientState) ExportMetadata(store sdk.KVStore) []exported.GenesisMetadata { + gm := make([]exported.GenesisMetadata, 0) + IterateConsensusMetadata(store, func(key, val []byte) bool { + gm = append(gm, clienttypes.NewGenesisMetadata(key, val)) + return false + }) + if len(gm) == 0 { + return nil + } + return gm +} diff --git a/modules/light-clients/01-dymint/types/genesis_test.go b/modules/light-clients/01-dymint/types/genesis_test.go new file mode 100644 index 00000000000..e1b71713c98 --- /dev/null +++ b/modules/light-clients/01-dymint/types/genesis_test.go @@ -0,0 +1,89 @@ +package types_test + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" + ibctesting "github.com/cosmos/ibc-go/v3/testing" +) + +// expected export ordering: +// processed height and processed time per height +// then all iteration keys +func (suite *DymintTestSuite) TestExportMetadata() { + // test intializing client and exporting metadata + path := ibctesting.NewPath(suite.chainA, suite.chainB) + suite.coordinator.SetupClients(path) + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + clientState := path.EndpointA.GetClientState() + height := clientState.GetLatestHeight() + + initIteration := types.GetIterationKey(clientStore, height) + suite.Require().NotEqual(0, len(initIteration)) + initProcessedTime, found := types.GetProcessedTime(clientStore, height) + suite.Require().True(found) + initProcessedHeight, found := types.GetProcessedHeight(clientStore, height) + suite.Require().True(found) + + gm := clientState.ExportMetadata(suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID)) + suite.Require().NotNil(gm, "client with metadata returned nil exported metadata") + suite.Require().Len(gm, 3, "exported metadata has unexpected length") + + suite.Require().Equal(types.ProcessedHeightKey(height), gm[0].GetKey(), "metadata has unexpected key") + actualProcessedHeight, err := clienttypes.ParseHeight(string(gm[0].GetValue())) + suite.Require().NoError(err) + suite.Require().Equal(initProcessedHeight, actualProcessedHeight, "metadata has unexpected value") + + suite.Require().Equal(types.ProcessedTimeKey(height), gm[1].GetKey(), "metadata has unexpected key") + suite.Require().Equal(initProcessedTime, sdk.BigEndianToUint64(gm[1].GetValue()), "metadata has unexpected value") + + suite.Require().Equal(types.IterationKey(height), gm[2].GetKey(), "metadata has unexpected key") + suite.Require().Equal(initIteration, gm[2].GetValue(), "metadata has unexpected value") + + // test updating client and exporting metadata + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + clientState = path.EndpointA.GetClientState() + updateHeight := clientState.GetLatestHeight() + + iteration := types.GetIterationKey(clientStore, updateHeight) + suite.Require().NotEqual(0, len(initIteration)) + processedTime, found := types.GetProcessedTime(clientStore, updateHeight) + suite.Require().True(found) + processedHeight, found := types.GetProcessedHeight(clientStore, updateHeight) + suite.Require().True(found) + + gm = clientState.ExportMetadata(suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID)) + suite.Require().NotNil(gm, "client with metadata returned nil exported metadata") + suite.Require().Len(gm, 6, "exported metadata has unexpected length") + + // expected ordering: + // initProcessedHeight, initProcessedTime, processedHeight, processedTime, initIteration, iteration + + // check init processed height and time + suite.Require().Equal(types.ProcessedHeightKey(height), gm[0].GetKey(), "metadata has unexpected key") + actualProcessedHeight, err = clienttypes.ParseHeight(string(gm[0].GetValue())) + suite.Require().NoError(err) + suite.Require().Equal(initProcessedHeight, actualProcessedHeight, "metadata has unexpected value") + + suite.Require().Equal(types.ProcessedTimeKey(height), gm[1].GetKey(), "metadata has unexpected key") + suite.Require().Equal(initProcessedTime, sdk.BigEndianToUint64(gm[1].GetValue()), "metadata has unexpected value") + + // check processed height and time after update + suite.Require().Equal(types.ProcessedHeightKey(updateHeight), gm[2].GetKey(), "metadata has unexpected key") + actualProcessedHeight, err = clienttypes.ParseHeight(string(gm[2].GetValue())) + suite.Require().NoError(err) + suite.Require().Equal(processedHeight, actualProcessedHeight, "metadata has unexpected value") + + suite.Require().Equal(types.ProcessedTimeKey(updateHeight), gm[3].GetKey(), "metadata has unexpected key") + suite.Require().Equal(processedTime, sdk.BigEndianToUint64(gm[3].GetValue()), "metadata has unexpected value") + + // check iteration keys + suite.Require().Equal(types.IterationKey(height), gm[4].GetKey(), "metadata has unexpected key") + suite.Require().Equal(initIteration, gm[4].GetValue(), "metadata has unexpected value") + + suite.Require().Equal(types.IterationKey(updateHeight), gm[5].GetKey(), "metadata has unexpected key") + suite.Require().Equal(iteration, gm[5].GetValue(), "metadata has unexpected value") +} diff --git a/modules/light-clients/01-dymint/types/header.go b/modules/light-clients/01-dymint/types/header.go new file mode 100644 index 00000000000..3e73b5562a0 --- /dev/null +++ b/modules/light-clients/01-dymint/types/header.go @@ -0,0 +1,82 @@ +package types + +import ( + "bytes" + "time" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + tmtypes "github.com/tendermint/tendermint/types" + + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" +) + +var _ exported.Header = &Header{} + +// 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 Dymint rollapp +func (h Header) ClientType() string { + return exported.Dymint +} + +// GetHeight returns the current height. It returns 0 if the dymint +// 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 dymint 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 sdkerrors.Wrap(clienttypes.ErrInvalidHeader, "dymint signed header cannot be nil") + } + if h.Header == nil { + return sdkerrors.Wrap(clienttypes.ErrInvalidHeader, "dymint header cannot be nil") + } + tmSignedHeader, err := tmtypes.SignedHeaderFromProto(h.SignedHeader) + if err != nil { + return sdkerrors.Wrap(err, "header is not a dymint header") + } + if err := tmSignedHeader.ValidateBasic(h.Header.GetChainID()); err != nil { + return sdkerrors.Wrap(err, "header failed basic validation") + } + + // TrustedHeight is less than Header for updates and misbehaviour + if h.TrustedHeight.GTE(h.GetHeight()) { + return sdkerrors.Wrapf(ErrInvalidHeaderHeight, "TrustedHeight %d must be less than header height %d", + h.TrustedHeight, h.GetHeight()) + } + + if h.ValidatorSet == nil { + return sdkerrors.Wrap(clienttypes.ErrInvalidHeader, "validator set is nil") + } + tmValset, err := tmtypes.ValidatorSetFromProto(h.ValidatorSet) + if err != nil { + return sdkerrors.Wrap(err, "validator set is not dymint validator set") + } + if !bytes.Equal(h.Header.ValidatorsHash, tmValset.Hash()) { + return sdkerrors.Wrap(clienttypes.ErrInvalidHeader, "validator set does not match hash") + } + return nil +} diff --git a/modules/light-clients/01-dymint/types/header_test.go b/modules/light-clients/01-dymint/types/header_test.go new file mode 100644 index 00000000000..17b0aaac708 --- /dev/null +++ b/modules/light-clients/01-dymint/types/header_test.go @@ -0,0 +1,83 @@ +package types_test + +import ( + "time" + + tmprotocrypto "github.com/tendermint/tendermint/proto/tendermint/crypto" + + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" + "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" + ibctesting "github.com/cosmos/ibc-go/v3/testing" +) + +func (suite *DymintTestSuite) TestGetHeight() { + header := suite.chainA.TestChainClient.(*ibctesting.TestChainDymint).LastHeader + suite.Require().NotEqual(uint64(0), header.GetHeight()) +} + +func (suite *DymintTestSuite) TestGetTime() { + header := suite.chainA.TestChainClient.(*ibctesting.TestChainDymint).LastHeader + suite.Require().NotEqual(time.Time{}, header.GetTime()) +} + +func (suite *DymintTestSuite) TestHeaderValidateBasic() { + var ( + header *types.Header + ) + testCases := []struct { + name string + malleate func() + expPass bool + }{ + {"valid header", func() {}, true}, + {"header is nil", func() { + header.Header = nil + }, false}, + {"signed header is nil", func() { + header.SignedHeader = nil + }, false}, + {"SignedHeaderFromProto failed", func() { + header.SignedHeader.Commit.Height = -1 + }, false}, + {"signed header failed dymint ValidateBasic", func() { + header = suite.chainA.TestChainClient.(*ibctesting.TestChainDymint).LastHeader + header.SignedHeader.Commit = nil + }, false}, + {"trusted height is equal to header height", func() { + header.TrustedHeight = header.GetHeight().(clienttypes.Height) + }, false}, + {"validator set nil", func() { + header.ValidatorSet = nil + }, false}, + {"ValidatorSetFromProto failed", func() { + header.ValidatorSet.Validators[0].PubKey = tmprotocrypto.PublicKey{} + }, false}, + {"header validator hash does not equal hash of validator set", func() { + // use chainB's randomly generated validator set + header.ValidatorSet = suite.chainB.TestChainClient.(*ibctesting.TestChainDymint).LastHeader.ValidatorSet + }, false}, + } + + suite.Require().Equal(exported.Dymint, suite.header.ClientType()) + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() + + header = suite.chainA.TestChainClient.(*ibctesting.TestChainDymint).LastHeader // must be explicitly changed in malleate + + tc.malleate() + + err := header.ValidateBasic() + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} diff --git a/modules/light-clients/01-dymint/types/misbehaviour.go b/modules/light-clients/01-dymint/types/misbehaviour.go new file mode 100644 index 00000000000..8bfbfe87b26 --- /dev/null +++ b/modules/light-clients/01-dymint/types/misbehaviour.go @@ -0,0 +1,132 @@ +package types + +import ( + "time" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + tmtypes "github.com/tendermint/tendermint/types" + + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v3/modules/core/24-host" + "github.com/cosmos/ibc-go/v3/modules/core/exported" +) + +var _ exported.Misbehaviour = &Misbehaviour{} + +// Use the same FrozenHeight 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 Dymint light client +func (misbehaviour Misbehaviour) ClientType() string { + return exported.Dymint +} + +// GetClientID returns the ID of the client that committed a misbehaviour. +func (misbehaviour Misbehaviour) GetClientID() string { + return misbehaviour.ClientId +} + +// 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 sdkerrors.Wrap(ErrInvalidHeader, "misbehaviour Header1 cannot be nil") + } + if misbehaviour.Header2 == nil { + return sdkerrors.Wrap(ErrInvalidHeader, "misbehaviour Header2 cannot be nil") + } + if misbehaviour.Header1.TrustedHeight.RevisionHeight == 0 { + return sdkerrors.Wrapf(ErrInvalidHeaderHeight, "misbehaviour Header1 cannot have zero revision height") + } + if misbehaviour.Header2.TrustedHeight.RevisionHeight == 0 { + return sdkerrors.Wrapf(ErrInvalidHeaderHeight, "misbehaviour Header2 cannot have zero revision height") + } + if misbehaviour.Header1.TrustedValidators == nil { + return sdkerrors.Wrap(ErrInvalidValidatorSet, "trusted validator set in Header1 cannot be empty") + } + if misbehaviour.Header2.TrustedValidators == nil { + return sdkerrors.Wrap(ErrInvalidValidatorSet, "trusted validator set in Header2 cannot be empty") + } + if misbehaviour.Header1.Header.ChainID != misbehaviour.Header2.Header.ChainID { + return sdkerrors.Wrap(clienttypes.ErrInvalidMisbehaviour, "headers must have identical chainIDs") + } + + if err := host.ClientIdentifierValidator(misbehaviour.ClientId); err != nil { + return sdkerrors.Wrap(err, "misbehaviour client ID is invalid") + } + + // ValidateBasic on both validators + if err := misbehaviour.Header1.ValidateBasic(); err != nil { + return sdkerrors.Wrap( + clienttypes.ErrInvalidMisbehaviour, + sdkerrors.Wrap(err, "header 1 failed validation").Error(), + ) + } + if err := misbehaviour.Header2.ValidateBasic(); err != nil { + return sdkerrors.Wrap( + clienttypes.ErrInvalidMisbehaviour, + sdkerrors.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 sdkerrors.Wrapf(clienttypes.ErrInvalidMisbehaviour, "Header1 height is less than Header2 height (%s < %s)", misbehaviour.Header1.GetHeight(), misbehaviour.Header2.GetHeight()) + } + + blockID1, err := tmtypes.BlockIDFromProto(&misbehaviour.Header1.SignedHeader.Commit.BlockID) + if err != nil { + return sdkerrors.Wrap(err, "invalid block ID from header 1 in misbehaviour") + } + blockID2, err := tmtypes.BlockIDFromProto(&misbehaviour.Header2.SignedHeader.Commit.BlockID) + if err != nil { + return sdkerrors.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 + } + if err := validCommit(misbehaviour.Header2.Header.ChainID, *blockID2, + misbehaviour.Header2.Commit, misbehaviour.Header2.ValidatorSet); err != nil { + return err + } + return nil +} + +// validCommit checks if the given commit is a valid commit from the passed-in validatorset +func validCommit(chainID string, blockID tmtypes.BlockID, commit *tmproto.Commit, valSet *tmproto.ValidatorSet) (err error) { + tmCommit, err := tmtypes.CommitFromProto(commit) + if err != nil { + return sdkerrors.Wrap(err, "commit is not dymint commit type") + } + tmValset, err := tmtypes.ValidatorSetFromProto(valSet) + if err != nil { + return sdkerrors.Wrap(err, "validator set is not dymint validator set type") + } + + if err := tmValset.VerifyCommitLight(chainID, blockID, tmCommit.Height, tmCommit); err != nil { + return sdkerrors.Wrap(clienttypes.ErrInvalidMisbehaviour, "validator set did not commit to header") + } + + return nil +} diff --git a/modules/light-clients/01-dymint/types/misbehaviour_handle.go b/modules/light-clients/01-dymint/types/misbehaviour_handle.go new file mode 100644 index 00000000000..ef4b829a382 --- /dev/null +++ b/modules/light-clients/01-dymint/types/misbehaviour_handle.go @@ -0,0 +1,144 @@ +package types + +import ( + "bytes" + "time" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + tmtypes "github.com/tendermint/tendermint/types" + + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" +) + +// CheckMisbehaviourAndUpdateState 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) CheckMisbehaviourAndUpdateState( + ctx sdk.Context, + cdc codec.BinaryCodec, + clientStore sdk.KVStore, + misbehaviour exported.Misbehaviour, +) (exported.ClientState, error) { + tmMisbehaviour, ok := misbehaviour.(*Misbehaviour) + if !ok { + return nil, sdkerrors.Wrapf(clienttypes.ErrInvalidClientType, "expected type %T, got %T", misbehaviour, &Misbehaviour{}) + } + + // The status of the client is checked in 02-client + + // 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 tmMisbehaviour.Header1.GetHeight().EQ(tmMisbehaviour.Header2.GetHeight()) { + blockID1, err := tmtypes.BlockIDFromProto(&tmMisbehaviour.Header1.SignedHeader.Commit.BlockID) + if err != nil { + return nil, sdkerrors.Wrap(err, "invalid block ID from header 1 in misbehaviour") + } + blockID2, err := tmtypes.BlockIDFromProto(&tmMisbehaviour.Header2.SignedHeader.Commit.BlockID) + if err != nil { + return nil, sdkerrors.Wrap(err, "invalid block ID from header 2 in misbehaviour") + } + + // Ensure that Commit Hashes are different + if bytes.Equal(blockID1.Hash, blockID2.Hash) { + return nil, sdkerrors.Wrap(clienttypes.ErrInvalidMisbehaviour, "headers block hashes are equal") + } + } else { + // 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). + if tmMisbehaviour.Header1.SignedHeader.Header.Time.After(tmMisbehaviour.Header2.SignedHeader.Header.Time) { + return nil, sdkerrors.Wrap(clienttypes.ErrInvalidMisbehaviour, "headers are not at same height and are monotonically increasing") + } + } + + // 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 + // and unmarshal from clientStore + + // Get consensus bytes from clientStore + tmConsensusState1, err := GetConsensusState(clientStore, cdc, tmMisbehaviour.Header1.TrustedHeight) + if err != nil { + return nil, sdkerrors.Wrapf(err, "could not get trusted consensus state from clientStore for Header1 at TrustedHeight: %s", tmMisbehaviour.Header1) + } + + // Get consensus bytes from clientStore + tmConsensusState2, err := GetConsensusState(clientStore, cdc, tmMisbehaviour.Header2.TrustedHeight) + if err != nil { + return nil, sdkerrors.Wrapf(err, "could not get trusted consensus state from clientStore for Header2 at TrustedHeight: %s", tmMisbehaviour.Header2) + } + + // 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, tmMisbehaviour.Header1, ctx.BlockTime(), + ); err != nil { + return nil, sdkerrors.Wrap(err, "verifying Header1 in Misbehaviour failed") + } + if err := checkMisbehaviourHeader( + &cs, tmConsensusState2, tmMisbehaviour.Header2, ctx.BlockTime(), + ); err != nil { + return nil, sdkerrors.Wrap(err, "verifying Header2 in Misbehaviour failed") + } + + cs.FrozenHeight = FrozenHeight + + return &cs, 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 := tmtypes.ValidatorSetFromProto(header.TrustedValidators) + if err != nil { + return sdkerrors.Wrap(err, "trusted validator set is not dymint validator set type") + } + + tmCommit, err := tmtypes.CommitFromProto(header.Commit) + if err != nil { + return sdkerrors.Wrap(err, "commit is not dymint 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 sdkerrors.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 + 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.ToDymint(), + ); err != nil { + return sdkerrors.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/01-dymint/types/misbehaviour_handle_test.go b/modules/light-clients/01-dymint/types/misbehaviour_handle_test.go new file mode 100644 index 00000000000..f450b6d7f8b --- /dev/null +++ b/modules/light-clients/01-dymint/types/misbehaviour_handle_test.go @@ -0,0 +1,432 @@ +package types_test + +import ( + "fmt" + "time" + + "github.com/tendermint/tendermint/crypto/tmhash" + tmtypes "github.com/tendermint/tendermint/types" + + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" + "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" + ibctesting "github.com/cosmos/ibc-go/v3/testing" + ibctestingmock "github.com/cosmos/ibc-go/v3/testing/mock" +) + +func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { + altPrivVal := ibctestingmock.NewPV() + altPubKey, err := altPrivVal.GetPubKey() + suite.Require().NoError(err) + + altVal := tmtypes.NewValidator(altPubKey, 4) + + // Create bothValSet with both suite validator and altVal + bothValSet := tmtypes.NewValidatorSet(append(suite.valSet.Validators, altVal)) + bothValsHash := bothValSet.Hash() + // Create alternative validator set with only altVal + altValSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{altVal}) + + _, suiteVal := suite.valSet.GetByIndex(0) + + // Create signer array and ensure it is in same order as bothValSet + bothSigners := ibctesting.CreateSortedSignerArray(altPrivVal, suite.privVal, altVal, suiteVal) + + altSigners := []tmtypes.PrivValidator{altPrivVal} + + heightMinus1 := clienttypes.NewHeight(height.RevisionNumber, height.RevisionHeight-1) + heightMinus3 := clienttypes.NewHeight(height.RevisionNumber, height.RevisionHeight-3) + + chainADymint := suite.chainA.TestChainClient.(*ibctesting.TestChainDymint) + + testCases := []struct { + name string + clientState exported.ClientState + consensusState1 exported.ConsensusState + height1 clienttypes.Height + consensusState2 exported.ConsensusState + height2 clienttypes.Height + misbehaviour exported.Misbehaviour + timestamp time.Time + expPass bool + }{ + { + "valid fork misbehaviour", + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + &types.Misbehaviour{ + Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + ClientId: chainID, + }, + suite.now, + true, + }, + { + "valid time misbehaviour", + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + &types.Misbehaviour{ + Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+3), height, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), + ClientId: chainID, + }, + suite.now, + true, + }, + { + "valid time misbehaviour header 1 stricly less than header 2", + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + &types.Misbehaviour{ + Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+3), height, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Hour), bothValSet, bothValSet, bothSigners), + ClientId: chainID, + }, + suite.now, + true, + }, + { + "valid misbehavior at height greater than last consensusState", + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + heightMinus1, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + heightMinus1, + &types.Misbehaviour{ + Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + ClientId: chainID, + }, + suite.now, + true, + }, + { + "valid misbehaviour with different trusted heights", + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + heightMinus1, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), + heightMinus3, + &types.Misbehaviour{ + Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus3, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), + ClientId: chainID, + }, + suite.now, + true, + }, + { + "valid misbehaviour at a previous revision", + types.NewClientState(chainIDRevision1, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + heightMinus1, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), + heightMinus3, + &types.Misbehaviour{ + Header1: chainADymint.CreateDMClientHeader(chainIDRevision0, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainADymint.CreateDMClientHeader(chainIDRevision0, int64(height.RevisionHeight+1), heightMinus3, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), + ClientId: chainID, + }, + suite.now, + true, + }, + { + "valid misbehaviour at a future revision", + types.NewClientState(chainIDRevision0, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + heightMinus1, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), + heightMinus3, + &types.Misbehaviour{ + Header1: chainADymint.CreateDMClientHeader(chainIDRevision0, 3, heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainADymint.CreateDMClientHeader(chainIDRevision0, 3, heightMinus3, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), + ClientId: chainID, + }, + suite.now, + true, + }, + { + "valid misbehaviour with trusted heights at a previous revision", + types.NewClientState(chainIDRevision1, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + heightMinus1, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), + heightMinus3, + &types.Misbehaviour{ + Header1: chainADymint.CreateDMClientHeader(chainIDRevision1, 1, heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainADymint.CreateDMClientHeader(chainIDRevision1, 1, heightMinus3, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), + ClientId: chainID, + }, + suite.now, + true, + }, + { + "consensus state's valset hash different from misbehaviour should still pass", + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), + height, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), + height, + &types.Misbehaviour{ + Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, suite.valSet, bothSigners), + Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), + ClientId: chainID, + }, + suite.now, + true, + }, + { + "invalid fork misbehaviour: identical headers", + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + &types.Misbehaviour{ + Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), + ClientId: chainID, + }, + suite.now, + false, + }, + { + "invalid time misbehaviour: monotonically increasing time", + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + &types.Misbehaviour{ + Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+3), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), + ClientId: chainID, + }, + suite.now, + false, + }, + { + "invalid misbehavior misbehaviour from different chain", + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + &types.Misbehaviour{ + Header1: chainADymint.CreateDMClientHeader("ethermint", int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainADymint.CreateDMClientHeader("ethermint", int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + ClientId: chainID, + }, + suite.now, + false, + }, + { + "invalid misbehavior misbehaviour with trusted height different from trusted consensus state", + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + heightMinus1, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), + heightMinus3, + &types.Misbehaviour{ + Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), + ClientId: chainID, + }, + suite.now, + false, + }, + { + "invalid misbehavior misbehaviour with trusted validators different from trusted consensus state", + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + heightMinus1, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), + heightMinus3, + &types.Misbehaviour{ + Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus3, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + ClientId: chainID, + }, + suite.now, + false, + }, + { + "already frozen client state", + &types.ClientState{FrozenHeight: clienttypes.NewHeight(0, 1)}, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + &types.Misbehaviour{ + Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + ClientId: chainID, + }, + suite.now, + false, + }, + { + "trusted consensus state does not exist", + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + nil, // consensus state for trusted height - 1 does not exist in store + clienttypes.Height{}, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + &types.Misbehaviour{ + Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + ClientId: chainID, + }, + suite.now, + false, + }, + { + "invalid dymint misbehaviour", + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + nil, + suite.now, + false, + }, + { + "provided height > header height", + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + &types.Misbehaviour{ + Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + ClientId: chainID, + }, + suite.now, + false, + }, + { + "trusting period expired", + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewConsensusState(time.Time{}, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + heightMinus1, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + &types.Misbehaviour{ + Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + ClientId: chainID, + }, + suite.now.Add(trustingPeriod), + false, + }, + { + "trusted validators is incorrect for given consensus state", + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + &types.Misbehaviour{ + Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, suite.valSet, bothSigners), + Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), + ClientId: chainID, + }, + suite.now, + false, + }, + { + "first valset has too much change", + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + &types.Misbehaviour{ + Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, altValSet, bothValSet, altSigners), + Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + ClientId: chainID, + }, + suite.now, + false, + }, + { + "second valset has too much change", + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + &types.Misbehaviour{ + Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), altValSet, bothValSet, altSigners), + ClientId: chainID, + }, + suite.now, + false, + }, + { + "both valsets have too much change", + types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), + height, + &types.Misbehaviour{ + Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, altValSet, bothValSet, altSigners), + Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), altValSet, bothValSet, altSigners), + ClientId: chainID, + }, + suite.now, + false, + }, + } + + for i, tc := range testCases { + tc := tc + suite.Run(fmt.Sprintf("Case: %s", tc.name), func() { + // reset suite to create fresh application state + suite.SetupTest() + + // Set current timestamp in context + ctx := suite.chainA.GetContext().WithBlockTime(tc.timestamp) + + // Set trusted consensus states in client store + + if tc.consensusState1 != nil { + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(ctx, clientID, tc.height1, tc.consensusState1) + } + if tc.consensusState2 != nil { + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(ctx, clientID, tc.height2, tc.consensusState2) + } + + clientState, err := tc.clientState.CheckMisbehaviourAndUpdateState( + ctx, + suite.chainA.App.AppCodec(), + suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, clientID), // pass in clientID prefixed clientStore + tc.misbehaviour, + ) + + if tc.expPass { + suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) + suite.Require().NotNil(clientState, "valid test case %d failed: %s", i, tc.name) + suite.Require().True(!clientState.(*types.ClientState).FrozenHeight.IsZero(), "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().Nil(clientState, "invalid test case %d passed: %s", i, tc.name) + } + }) + } +} diff --git a/modules/light-clients/01-dymint/types/misbehaviour_test.go b/modules/light-clients/01-dymint/types/misbehaviour_test.go new file mode 100644 index 00000000000..9c57fd905ff --- /dev/null +++ b/modules/light-clients/01-dymint/types/misbehaviour_test.go @@ -0,0 +1,247 @@ +package types_test + +import ( + "time" + + "github.com/tendermint/tendermint/crypto/tmhash" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + tmtypes "github.com/tendermint/tendermint/types" + + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" + "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" + ibctesting "github.com/cosmos/ibc-go/v3/testing" + ibctestingmock "github.com/cosmos/ibc-go/v3/testing/mock" +) + +func (suite *DymintTestSuite) TestMisbehaviour() { + signers := []tmtypes.PrivValidator{suite.privVal} + heightMinus1 := clienttypes.NewHeight(0, height.RevisionHeight-1) + + chainADymint := suite.chainA.TestChainClient.(*ibctesting.TestChainDymint) + + misbehaviour := &types.Misbehaviour{ + Header1: suite.header, + Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, signers), + ClientId: clientID, + } + + suite.Require().Equal(exported.Dymint, misbehaviour.ClientType()) + suite.Require().Equal(clientID, misbehaviour.GetClientID()) +} + +func (suite *DymintTestSuite) TestMisbehaviourValidateBasic() { + altPrivVal := ibctestingmock.NewPV() + altPubKey, err := altPrivVal.GetPubKey() + suite.Require().NoError(err) + + revisionHeight := int64(height.RevisionHeight) + + altVal := tmtypes.NewValidator(altPubKey, revisionHeight) + + // Create bothValSet with both suite validator and altVal + bothValSet := tmtypes.NewValidatorSet(append(suite.valSet.Validators, altVal)) + // Create alternative validator set with only altVal + altValSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{altVal}) + + signers := []tmtypes.PrivValidator{suite.privVal} + + // Create signer array and ensure it is in same order as bothValSet + _, suiteVal := suite.valSet.GetByIndex(0) + bothSigners := ibctesting.CreateSortedSignerArray(altPrivVal, suite.privVal, altVal, suiteVal) + + altSigners := []tmtypes.PrivValidator{altPrivVal} + + heightMinus1 := clienttypes.NewHeight(0, height.RevisionHeight-1) + + chainADymint := suite.chainA.TestChainClient.(*ibctesting.TestChainDymint) + + testCases := []struct { + name string + misbehaviour *types.Misbehaviour + malleateMisbehaviour func(misbehaviour *types.Misbehaviour) error + expPass bool + }{ + { + "valid fork misbehaviour, two headers at same height have different time", + &types.Misbehaviour{ + Header1: suite.header, + Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers), + ClientId: clientID, + }, + func(misbehaviour *types.Misbehaviour) error { return nil }, + true, + }, + { + "valid time misbehaviour, both headers at different heights are at same time", + &types.Misbehaviour{ + Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+5), heightMinus1, suite.now, suite.valSet, suite.valSet, signers), + Header2: suite.header, + ClientId: clientID, + }, + func(misbehaviour *types.Misbehaviour) error { return nil }, + true, + }, + { + "misbehaviour Header1 is nil", + types.NewMisbehaviour(clientID, nil, suite.header), + func(m *types.Misbehaviour) error { return nil }, + false, + }, + { + "misbehaviour Header2 is nil", + types.NewMisbehaviour(clientID, suite.header, nil), + func(m *types.Misbehaviour) error { return nil }, + false, + }, + { + "valid misbehaviour with different trusted headers", + &types.Misbehaviour{ + Header1: suite.header, + Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), clienttypes.NewHeight(0, height.RevisionHeight-3), suite.now.Add(time.Minute), suite.valSet, bothValSet, signers), + ClientId: clientID, + }, + func(misbehaviour *types.Misbehaviour) error { return nil }, + true, + }, + { + "trusted height is 0 in Header1", + &types.Misbehaviour{ + Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), clienttypes.ZeroHeight(), suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers), + Header2: suite.header, + ClientId: clientID, + }, + func(misbehaviour *types.Misbehaviour) error { return nil }, + false, + }, + { + "trusted height is 0 in Header2", + &types.Misbehaviour{ + Header1: suite.header, + Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), clienttypes.ZeroHeight(), suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers), + ClientId: clientID, + }, + func(misbehaviour *types.Misbehaviour) error { return nil }, + false, + }, + { + "trusted valset is nil in Header1", + &types.Misbehaviour{ + Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now.Add(time.Minute), suite.valSet, nil, signers), + Header2: suite.header, + ClientId: clientID, + }, + func(misbehaviour *types.Misbehaviour) error { return nil }, + false, + }, + { + "trusted valset is nil in Header2", + &types.Misbehaviour{ + Header1: suite.header, + Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now.Add(time.Minute), suite.valSet, nil, signers), + ClientId: clientID, + }, + func(misbehaviour *types.Misbehaviour) error { return nil }, + false, + }, + { + "invalid client ID ", + &types.Misbehaviour{ + Header1: suite.header, + Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, signers), + ClientId: "GAIA", + }, + func(misbehaviour *types.Misbehaviour) error { return nil }, + false, + }, + { + "chainIDs do not match", + &types.Misbehaviour{ + Header1: suite.header, + Header2: chainADymint.CreateDMClientHeader("ethermint", int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, signers), + ClientId: clientID, + }, + func(misbehaviour *types.Misbehaviour) error { return nil }, + false, + }, + { + "header2 height is greater", + &types.Misbehaviour{ + Header1: suite.header, + Header2: chainADymint.CreateDMClientHeader(chainID, 6, clienttypes.NewHeight(0, height.RevisionHeight+1), suite.now, suite.valSet, suite.valSet, signers), + ClientId: clientID, + }, + func(misbehaviour *types.Misbehaviour) error { return nil }, + false, + }, + { + "header 1 doesn't have 2/3 majority", + &types.Misbehaviour{ + Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, bothValSet, suite.valSet, bothSigners), + Header2: suite.header, + ClientId: clientID, + }, + func(misbehaviour *types.Misbehaviour) error { + // voteSet contains only altVal which is less than 2/3 of total power (height/1height) + wrongVoteSet := tmtypes.NewVoteSet(chainID, int64(misbehaviour.Header1.GetHeight().GetRevisionHeight()), 1, tmproto.PrecommitType, altValSet) + blockID, err := tmtypes.BlockIDFromProto(&misbehaviour.Header1.Commit.BlockID) + if err != nil { + return err + } + + tmCommit, err := tmtypes.MakeCommit(*blockID, int64(misbehaviour.Header2.GetHeight().GetRevisionHeight()), misbehaviour.Header1.Commit.Round, wrongVoteSet, altSigners, suite.now) + misbehaviour.Header1.Commit = tmCommit.ToProto() + return err + }, + false, + }, + { + "header 2 doesn't have 2/3 majority", + &types.Misbehaviour{ + Header1: suite.header, + Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, bothValSet, suite.valSet, bothSigners), + ClientId: clientID, + }, + func(misbehaviour *types.Misbehaviour) error { + // voteSet contains only altVal which is less than 2/3 of total power (height/1height) + wrongVoteSet := tmtypes.NewVoteSet(chainID, int64(misbehaviour.Header2.GetHeight().GetRevisionHeight()), 1, tmproto.PrecommitType, altValSet) + blockID, err := tmtypes.BlockIDFromProto(&misbehaviour.Header2.Commit.BlockID) + if err != nil { + return err + } + + tmCommit, err := tmtypes.MakeCommit(*blockID, int64(misbehaviour.Header2.GetHeight().GetRevisionHeight()), misbehaviour.Header2.Commit.Round, wrongVoteSet, altSigners, suite.now) + misbehaviour.Header2.Commit = tmCommit.ToProto() + return err + }, + false, + }, + { + "validators sign off on wrong commit", + &types.Misbehaviour{ + Header1: suite.header, + Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, bothValSet, suite.valSet, bothSigners), + ClientId: clientID, + }, + func(misbehaviour *types.Misbehaviour) error { + tmBlockID := ibctesting.MakeBlockID(tmhash.Sum([]byte("other_hash")), 3, tmhash.Sum([]byte("other_partset"))) + misbehaviour.Header2.Commit.BlockID = tmBlockID.ToProto() + return nil + }, + false, + }, + } + + for i, tc := range testCases { + tc := tc + + err := tc.malleateMisbehaviour(tc.misbehaviour) + suite.Require().NoError(err) + + if tc.expPass { + suite.Require().NoError(tc.misbehaviour.ValidateBasic(), "valid test case %d failed: %s", i, tc.name) + } else { + suite.Require().Error(tc.misbehaviour.ValidateBasic(), "invalid test case %d passed: %s", i, tc.name) + } + } +} diff --git a/modules/light-clients/01-dymint/types/proposal_handle.go b/modules/light-clients/01-dymint/types/proposal_handle.go new file mode 100644 index 00000000000..bfa7f242e92 --- /dev/null +++ b/modules/light-clients/01-dymint/types/proposal_handle.go @@ -0,0 +1,106 @@ +package types + +import ( + "reflect" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" +) + +// CheckSubstituteAndUpdateState will try to update the client with the state of the +// substitute if and only if the proposal passes and one of the following conditions are +// satisfied: +// 1) AllowUpdateAfterMisbehaviour and Status() == Frozen +// 2) AllowUpdateAfterExpiry=true and Status() == Expired +// +// 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. If a client is frozen and AllowUpdateAfterMisbehaviour +// is set to true, the client will be unexpired even if AllowUpdateAfterExpiry is set to false. +func (cs ClientState) CheckSubstituteAndUpdateState( + ctx sdk.Context, cdc codec.BinaryCodec, subjectClientStore, + substituteClientStore sdk.KVStore, substituteClient exported.ClientState, +) (exported.ClientState, error) { + substituteClientState, ok := substituteClient.(*ClientState) + if !ok { + return nil, sdkerrors.Wrapf( + clienttypes.ErrInvalidClient, "expected type %T, got %T", &ClientState{}, substituteClient, + ) + } + + if !IsMatchingClientState(cs, *substituteClientState) { + return nil, sdkerrors.Wrap(clienttypes.ErrInvalidSubstitute, "subject client state does not match substitute client state") + } + + switch cs.Status(ctx, subjectClientStore, cdc) { + + case exported.Frozen: + if !cs.AllowUpdateAfterMisbehaviour { + return nil, sdkerrors.Wrap(clienttypes.ErrUpdateClientFailed, "client is not allowed to be unfrozen") + } + + // unfreeze the client + cs.FrozenHeight = clienttypes.ZeroHeight() + + case exported.Expired: + if !cs.AllowUpdateAfterExpiry { + return nil, sdkerrors.Wrap(clienttypes.ErrUpdateClientFailed, "client is not allowed to be unexpired") + } + + default: + return nil, sdkerrors.Wrap(clienttypes.ErrUpdateClientFailed, "client cannot be updated with proposal") + } + + // copy consensus states and processed time from substitute to subject + // starting from initial height and ending on the latest height (inclusive) + height := substituteClientState.GetLatestHeight() + + consensusState, err := GetConsensusState(substituteClientStore, cdc, height) + if err != nil { + return nil, sdkerrors.Wrap(err, "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 nil, sdkerrors.Wrap(clienttypes.ErrUpdateClientFailed, "unable to retrieve processed height for substitute client latest height") + } + + processedTime, found := GetProcessedTime(substituteClientStore, height) + if !found { + return nil, sdkerrors.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 + + // no validation is necessary since the substitute is verified to be Active + // in 02-client. + + return &cs, nil +} + +// IsMatchingClientState returns true if all the client state parameters match +// except for frozen height, latest height, and 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() + substitute.LatestHeight = clienttypes.ZeroHeight() + substitute.FrozenHeight = clienttypes.ZeroHeight() + subject.ChainId = "" + substitute.ChainId = "" + + return reflect.DeepEqual(subject, substitute) +} diff --git a/modules/light-clients/01-dymint/types/proposal_handle_test.go b/modules/light-clients/01-dymint/types/proposal_handle_test.go new file mode 100644 index 00000000000..7443b42d9a8 --- /dev/null +++ b/modules/light-clients/01-dymint/types/proposal_handle_test.go @@ -0,0 +1,371 @@ +package types_test + +import ( + "time" + + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" + "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" + ibctesting "github.com/cosmos/ibc-go/v3/testing" +) + +var ( + frozenHeight = clienttypes.NewHeight(0, 1) +) + +func (suite *DymintTestSuite) 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() { + suite.coordinator.SetupClients(substitutePath) + substituteClientState = suite.chainA.GetClientState(substitutePath.EndpointA.ClientID).(*types.ClientState) + tmClientState, ok := substituteClientState.(*types.ClientState) + suite.Require().True(ok) + + tmClientState.ChainId = tmClientState.ChainId + "different chain" + }, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + + suite.SetupTest() // reset + subjectPath := ibctesting.NewPath(suite.chainA, suite.chainB) + substitutePath = ibctesting.NewPath(suite.chainA, suite.chainB) + + suite.coordinator.SetupClients(subjectPath) + subjectClientState := suite.chainA.GetClientState(subjectPath.EndpointA.ClientID).(*types.ClientState) + subjectClientState.AllowUpdateAfterMisbehaviour = true + subjectClientState.AllowUpdateAfterExpiry = true + + // 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) + + updatedClient, err := subjectClientState.CheckSubstituteAndUpdateState(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), subjectClientStore, substituteClientStore, substituteClientState) + suite.Require().Error(err) + suite.Require().Nil(updatedClient) + }) + } +} + +// to expire clients, time needs to be fast forwarded on both chainA and chainB. +// this is to prevent headers from failing when attempting to update later. +func (suite *DymintTestSuite) TestCheckSubstituteAndUpdateState() { + testCases := []struct { + name string + AllowUpdateAfterExpiry bool + AllowUpdateAfterMisbehaviour bool + FreezeClient bool + ExpireClient bool + expPass bool + }{ + { + name: "not allowed to be updated, not frozen or expired", + AllowUpdateAfterExpiry: false, + AllowUpdateAfterMisbehaviour: false, + FreezeClient: false, + ExpireClient: false, + expPass: false, + }, + { + name: "not allowed to be updated, client is frozen", + AllowUpdateAfterExpiry: false, + AllowUpdateAfterMisbehaviour: false, + FreezeClient: true, + ExpireClient: false, + expPass: false, + }, + { + name: "not allowed to be updated, client is expired", + AllowUpdateAfterExpiry: false, + AllowUpdateAfterMisbehaviour: false, + FreezeClient: false, + ExpireClient: true, + expPass: false, + }, + { + name: "not allowed to be updated, client is frozen and expired", + AllowUpdateAfterExpiry: false, + AllowUpdateAfterMisbehaviour: false, + FreezeClient: true, + ExpireClient: true, + expPass: false, + }, + { + name: "allowed to be updated only after misbehaviour, not frozen or expired", + AllowUpdateAfterExpiry: false, + AllowUpdateAfterMisbehaviour: true, + FreezeClient: false, + ExpireClient: false, + expPass: false, + }, + { + name: "allowed to be updated only after misbehaviour, client is expired", + AllowUpdateAfterExpiry: false, + AllowUpdateAfterMisbehaviour: true, + FreezeClient: false, + ExpireClient: true, + expPass: false, + }, + { + name: "allowed to be updated only after expiry, not frozen or expired", + AllowUpdateAfterExpiry: true, + AllowUpdateAfterMisbehaviour: false, + FreezeClient: false, + ExpireClient: false, + expPass: false, + }, + { + name: "allowed to be updated only after expiry, client is frozen", + AllowUpdateAfterExpiry: true, + AllowUpdateAfterMisbehaviour: false, + FreezeClient: true, + ExpireClient: false, + expPass: false, + }, + { + name: "PASS: allowed to be updated only after misbehaviour, client is frozen", + AllowUpdateAfterExpiry: false, + AllowUpdateAfterMisbehaviour: true, + FreezeClient: true, + ExpireClient: false, + expPass: true, + }, + { + name: "PASS: allowed to be updated only after misbehaviour, client is frozen and expired", + AllowUpdateAfterExpiry: false, + AllowUpdateAfterMisbehaviour: true, + FreezeClient: true, + ExpireClient: true, + expPass: true, + }, + { + name: "PASS: allowed to be updated only after expiry, client is expired", + AllowUpdateAfterExpiry: true, + AllowUpdateAfterMisbehaviour: false, + FreezeClient: false, + ExpireClient: true, + expPass: true, + }, + { + name: "allowed to be updated only after expiry, client is frozen and expired", + AllowUpdateAfterExpiry: true, + AllowUpdateAfterMisbehaviour: false, + FreezeClient: true, + ExpireClient: true, + expPass: false, + }, + { + name: "allowed to be updated after expiry and misbehaviour, not frozen or expired", + AllowUpdateAfterExpiry: true, + AllowUpdateAfterMisbehaviour: true, + FreezeClient: false, + ExpireClient: false, + expPass: false, + }, + { + name: "PASS: allowed to be updated after expiry and misbehaviour, client is frozen", + AllowUpdateAfterExpiry: true, + AllowUpdateAfterMisbehaviour: true, + FreezeClient: true, + ExpireClient: false, + expPass: true, + }, + { + name: "PASS: allowed to be updated after expiry and misbehaviour, client is expired", + AllowUpdateAfterExpiry: true, + AllowUpdateAfterMisbehaviour: true, + FreezeClient: false, + ExpireClient: true, + expPass: true, + }, + { + name: "PASS: allowed to be updated after expiry and misbehaviour, client is frozen and expired", + AllowUpdateAfterExpiry: true, + AllowUpdateAfterMisbehaviour: true, + FreezeClient: true, + ExpireClient: true, + expPass: true, + }, + } + + for _, tc := range testCases { + tc := tc + + // for each test case a header used for unexpiring clients and unfreezing + // a client are each tested to ensure that unexpiry headers cannot update + // a client when a unfreezing header is required. + suite.Run(tc.name, func() { + + // start by testing unexpiring the client + suite.SetupTest() // reset + + // construct subject using test case parameters + subjectPath := ibctesting.NewPath(suite.chainA, suite.chainB) + suite.coordinator.SetupClients(subjectPath) + subjectClientState := suite.chainA.GetClientState(subjectPath.EndpointA.ClientID).(*types.ClientState) + subjectClientState.AllowUpdateAfterExpiry = tc.AllowUpdateAfterExpiry + subjectClientState.AllowUpdateAfterMisbehaviour = tc.AllowUpdateAfterMisbehaviour + + // apply freezing or expiry as determined by the test case + if tc.FreezeClient { + subjectClientState.FrozenHeight = frozenHeight + } + if tc.ExpireClient { + // expire subject client + suite.coordinator.IncrementTimeBy(subjectClientState.TrustingPeriod) + suite.coordinator.CommitBlock(suite.chainA, suite.chainB) + } + + // construct the substitute to match the subject client + // NOTE: the substitute is explicitly created after the freezing or expiry occurs, + // primarily to prevent the substitute from becoming frozen. It also should be + // the natural flow of events in practice. The subject will become frozen/expired + // and a substitute will be created along with a governance proposal as a response + + substitutePath := ibctesting.NewPath(suite.chainA, suite.chainB) + suite.coordinator.SetupClients(substitutePath) + substituteClientState := suite.chainA.GetClientState(substitutePath.EndpointA.ClientID).(*types.ClientState) + substituteClientState.AllowUpdateAfterExpiry = tc.AllowUpdateAfterExpiry + substituteClientState.AllowUpdateAfterMisbehaviour = tc.AllowUpdateAfterMisbehaviour + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), substitutePath.EndpointA.ClientID, substituteClientState) + + // update substitute a few times + for i := 0; i < 3; i++ { + err := substitutePath.EndpointA.UpdateClient() + suite.Require().NoError(err) + // skip a block + suite.coordinator.CommitBlock(suite.chainA, suite.chainB) + } + + // get updated substitute + substituteClientState = suite.chainA.GetClientState(substitutePath.EndpointA.ClientID).(*types.ClientState) + + // 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.GetLatestHeight()) + expectedProcessedTime, found := types.GetProcessedTime(substituteClientStore, substituteClientState.GetLatestHeight()) + suite.Require().True(found) + expectedProcessedHeight, found := types.GetProcessedTime(substituteClientStore, substituteClientState.GetLatestHeight()) + suite.Require().True(found) + expectedIterationKey := types.GetIterationKey(substituteClientStore, substituteClientState.GetLatestHeight()) + + updatedClient, err := subjectClientState.CheckSubstituteAndUpdateState(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), subjectClientStore, substituteClientStore, substituteClientState) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(clienttypes.ZeroHeight(), updatedClient.(*types.ClientState).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.GetLatestHeight(), updatedClient.GetLatestHeight()) + subjectConsState := subjectPath.EndpointA.GetConsensusState(updatedClient.GetLatestHeight()) + subjectProcessedTime, found := types.GetProcessedTime(subjectClientStore, updatedClient.GetLatestHeight()) + suite.Require().True(found) + subjectProcessedHeight, found := types.GetProcessedTime(substituteClientStore, updatedClient.GetLatestHeight()) + suite.Require().True(found) + subjectIterationKey := types.GetIterationKey(substituteClientStore, updatedClient.GetLatestHeight()) + + 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.(*types.ClientState).ChainId) + } else { + suite.Require().Error(err) + suite.Require().Nil(updatedClient) + } + + }) + } +} + +func (suite *DymintTestSuite) TestIsMatchingClientState() { + var ( + subjectPath, substitutePath *ibctesting.Path + subjectClientState, substituteClientState *types.ClientState + ) + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "matching clients", func() { + subjectClientState = suite.chainA.GetClientState(subjectPath.EndpointA.ClientID).(*types.ClientState) + substituteClientState = suite.chainA.GetClientState(substitutePath.EndpointA.ClientID).(*types.ClientState) + }, 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, + }, + { + "not matching, trusting period is different", func() { + subjectClientState.TrustingPeriod = time.Duration(time.Hour * 10) + substituteClientState.TrustingPeriod = time.Duration(time.Hour * 1) + }, false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + subjectPath = ibctesting.NewPath(suite.chainA, suite.chainB) + substitutePath = ibctesting.NewPath(suite.chainA, suite.chainB) + suite.coordinator.SetupClients(subjectPath) + suite.coordinator.SetupClients(substitutePath) + + tc.malleate() + + suite.Require().Equal(tc.expPass, types.IsMatchingClientState(*subjectClientState, *substituteClientState)) + + }) + } +} diff --git a/modules/light-clients/01-dymint/types/self_client.go b/modules/light-clients/01-dymint/types/self_client.go new file mode 100644 index 00000000000..6f8967525cd --- /dev/null +++ b/modules/light-clients/01-dymint/types/self_client.go @@ -0,0 +1,112 @@ +package types + +import ( + "reflect" + "time" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/tendermint/tendermint/light" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" +) + +var _ exported.SelfClient = (*SelfClient)(nil) + +type SelfClient struct{} + +// NewClientState creates a new ClientState instance +func NewSelfClient() exported.SelfClient { + return &SelfClient{} +} + +// ValidateSelfClientState validates the client parameters for a client of the running chain +// This function is only used to validate the client state the counterparty stores for this chain +// Client must be in same revision as the executing chain +func (sc SelfClient) ValidateSelfClientState( + ctx sdk.Context, + expectedUbdPeriod time.Duration, + clientState exported.ClientState, +) error { + tmClient, ok := clientState.(*ClientState) + if !ok { + return sdkerrors.Wrapf(clienttypes.ErrInvalidClient, "client must be a Dymint client, expected: %T, got: %T", + &ClientState{}, tmClient) + } + + if !tmClient.FrozenHeight.IsZero() { + return clienttypes.ErrClientFrozen + } + + if ctx.ChainID() != tmClient.ChainId { + return sdkerrors.Wrapf(clienttypes.ErrInvalidClient, "invalid chain-id. expected: %s, got: %s", + ctx.ChainID(), tmClient.ChainId) + } + + revision := clienttypes.ParseChainID(ctx.ChainID()) + + // client must be in the same revision as executing chain + if tmClient.LatestHeight.RevisionNumber != revision { + return sdkerrors.Wrapf(clienttypes.ErrInvalidClient, "client is not in the same revision as the chain. expected revision: %d, got: %d", + tmClient.LatestHeight.RevisionNumber, revision) + } + + selfHeight := clienttypes.NewHeight(revision, uint64(ctx.BlockHeight())) + if tmClient.LatestHeight.GTE(selfHeight) { + return sdkerrors.Wrapf(clienttypes.ErrInvalidClient, "client has LatestHeight %d greater than or equal to chain height %d", + tmClient.LatestHeight, selfHeight) + } + + expectedProofSpecs := commitmenttypes.GetSDKSpecs() + if !reflect.DeepEqual(expectedProofSpecs, tmClient.ProofSpecs) { + return sdkerrors.Wrapf(clienttypes.ErrInvalidClient, "client has invalid proof specs. expected: %v got: %v", + expectedProofSpecs, tmClient.ProofSpecs) + } + + if err := light.ValidateTrustLevel(tmClient.TrustLevel.ToDymint()); err != nil { + return sdkerrors.Wrapf(clienttypes.ErrInvalidClient, "trust-level invalid: %v", err) + } + + if expectedUbdPeriod != tmClient.UnbondingPeriod { + return sdkerrors.Wrapf(clienttypes.ErrInvalidClient, "invalid unbonding period. expected: %s, got: %s", + expectedUbdPeriod, tmClient.UnbondingPeriod) + } + + if tmClient.UnbondingPeriod < tmClient.TrustingPeriod { + return sdkerrors.Wrapf(clienttypes.ErrInvalidClient, "unbonding period must be greater than trusting period. unbonding period (%d) < trusting period (%d)", + tmClient.UnbondingPeriod, tmClient.TrustingPeriod) + } + + if len(tmClient.UpgradePath) != 0 { + // For now, SDK IBC implementation assumes that upgrade path (if defined) is defined by SDK upgrade module + expectedUpgradePath := []string{upgradetypes.StoreKey, upgradetypes.KeyUpgradedIBCState} + if !reflect.DeepEqual(expectedUpgradePath, tmClient.UpgradePath) { + return sdkerrors.Wrapf(clienttypes.ErrInvalidClient, "upgrade path must be the upgrade path defined by upgrade module. expected %v, got %v", + expectedUpgradePath, tmClient.UpgradePath) + } + } + return nil +} + +func (sc SelfClient) GetSelfConsensusStateFromBlocHeader( + cdc codec.BinaryCodec, + blockHeader []byte, +) (exported.ConsensusState, error) { + // unmarshal block header + tmBlockHeader := &tmproto.Header{} + if err := cdc.Unmarshal(blockHeader, tmBlockHeader); err != nil { + return nil, sdkerrors.Wrapf(clienttypes.ErrInvalidHeader, "could not unmarshal block header: %v", err) + } + return NewConsensusState(tmBlockHeader.Time, + commitmenttypes.NewMerkleRoot(tmBlockHeader.GetAppHash()), + tmBlockHeader.NextValidatorsHash), nil +} + +func (sc SelfClient) ClientType() string { + return exported.Dymint +} diff --git a/modules/light-clients/01-dymint/types/store.go b/modules/light-clients/01-dymint/types/store.go new file mode 100644 index 00000000000..0a9a61dfcf5 --- /dev/null +++ b/modules/light-clients/01-dymint/types/store.go @@ -0,0 +1,371 @@ +package types + +import ( + "bytes" + "encoding/binary" + "strings" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v3/modules/core/24-host" + "github.com/cosmos/ibc-go/v3/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 neigboring +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") +) + +// SetConsensusState stores the consensus state at the given height. +func SetConsensusState(clientStore sdk.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. An error is returned if the consensus state does not exist. +func GetConsensusState(store sdk.KVStore, cdc codec.BinaryCodec, height exported.Height) (*ConsensusState, error) { + bz := store.Get(host.ConsensusStateKey(height)) + if bz == nil { + return nil, sdkerrors.Wrapf( + clienttypes.ErrConsensusStateNotFound, + "consensus state does not exist for height %s", height, + ) + } + + consensusStateI, err := clienttypes.UnmarshalConsensusState(cdc, bz) + if err != nil { + return nil, sdkerrors.Wrapf(clienttypes.ErrInvalidConsensus, "unmarshal error: %v", err) + } + + consensusState, ok := consensusStateI.(*ConsensusState) + if !ok { + return nil, sdkerrors.Wrapf( + clienttypes.ErrInvalidConsensus, + "invalid consensus type %T, expected %T", consensusState, &ConsensusState{}, + ) + } + + return consensusState, nil +} + +// deleteConsensusState deletes the consensus state at the given height +func deleteConsensusState(clientStore sdk.KVStore, height exported.Height) { + key := host.ConsensusStateKey(height) + clientStore.Delete(key) +} + +// IterateConsensusMetadata iterates through the prefix store and applies the callback. +// If the cb returns true, then iterator will close and stop. +func IterateConsensusMetadata(store sdk.KVStore, cb func(key, val []byte) bool) { + iterator := sdk.KVStorePrefixIterator(store, []byte(host.KeyConsensusStatePrefix)) + + // iterate over processed time and processed height + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + keySplit := strings.Split(string(iterator.Key()), "/") + // processed time key in prefix store has format: "consensusState//processedTime" + if len(keySplit) != 3 { + // ignore all consensus state keys + continue + + } + + if keySplit[2] != "processedTime" && keySplit[2] != "processedHeight" { + // only perform callback on consensus metadata + continue + } + + if cb(iterator.Key(), iterator.Value()) { + break + } + } + + // iterate over iteration keys + iterator = sdk.KVStorePrefixIterator(store, []byte(KeyIterateConsensusStatePrefix)) + + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + if cb(iterator.Key(), iterator.Value()) { + break + } + } +} + +// 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 dymint client's +// verification functions +func SetProcessedTime(clientStore sdk.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 dymint header. +// This is used to validate that a received packet has passed the time delay period. +func GetProcessedTime(clientStore sdk.KVStore, height exported.Height) (uint64, bool) { + key := ProcessedTimeKey(height) + bz := clientStore.Get(key) + if bz == nil { + return 0, false + } + return sdk.BigEndianToUint64(bz), true +} + +// deleteProcessedTime deletes the processedTime for a given height +func deleteProcessedTime(clientStore sdk.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 dymint client's +// verification functions +func SetProcessedHeight(clientStore sdk.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 dymint header. +// This is used to validate that a received packet has passed the block delay period. +func GetProcessedHeight(clientStore sdk.KVStore, height exported.Height) (exported.Height, bool) { + key := ProcessedHeightKey(height) + bz := clientStore.Get(key) + if bz == nil { + 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 sdk.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 sdk.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 sdk.KVStore, height exported.Height) []byte { + key := IterationKey(height) + return clientStore.Get(key) +} + +// deleteIterationKey deletes the iteration key for a given height +func deleteIterationKey(clientStore sdk.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 sdk.KVStore, cb func(height exported.Height) (stop bool)) error { + iterator := sdk.KVStorePrefixIterator(clientStore, []byte(KeyIterateConsensusStatePrefix)) + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + iterKey := iterator.Key() + height := GetHeightFromIterationKey(iterKey) + if cb(height) { + return nil + } + } + return nil +} + +// 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 consenus state. +// Otherwise, the iterator is already at the next consensus state so we can call iterator.Value() immediately. +func GetNextConsensusState(clientStore sdk.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 sdk.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. +func PruneAllExpiredConsensusStates( + ctx sdk.Context, clientStore sdk.KVStore, + cdc codec.BinaryCodec, clientState *ClientState, +) (err error) { + var heights []exported.Height + + pruneCb := func(height exported.Height) bool { + consState, err := GetConsensusState(clientStore, cdc, height) + // this error should never occur + if err != nil { + return true + } + + if clientState.IsExpired(consState.Timestamp, ctx.BlockTime()) { + heights = append(heights, height) + } + + return false + } + + IterateConsensusStateAscending(clientStore, pruneCb) + if err != nil { + return err + } + + for _, height := range heights { + deleteConsensusState(clientStore, height) + deleteConsensusMetadata(clientStore, height) + } + + return nil +} + +// Helper function for GetNextConsensusState and GetPreviousConsensusState +func getTmConsensusState(clientStore sdk.KVStore, cdc codec.BinaryCodec, key []byte) (*ConsensusState, bool) { + bz := clientStore.Get(key) + if bz == nil { + 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 dymint 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 sdk.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 sdk.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 sdk.KVStore, height exported.Height) { + deleteProcessedTime(clientStore, height) + deleteProcessedHeight(clientStore, height) + deleteIterationKey(clientStore, height) +} diff --git a/modules/light-clients/01-dymint/types/store_test.go b/modules/light-clients/01-dymint/types/store_test.go new file mode 100644 index 00000000000..a28c6bb3790 --- /dev/null +++ b/modules/light-clients/01-dymint/types/store_test.go @@ -0,0 +1,194 @@ +package types_test + +import ( + "math" + "time" + + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v3/modules/core/24-host" + "github.com/cosmos/ibc-go/v3/modules/core/exported" + "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" + solomachinetypes "github.com/cosmos/ibc-go/v3/modules/light-clients/06-solomachine/types" + ibctesting "github.com/cosmos/ibc-go/v3/testing" +) + +func (suite *DymintTestSuite) TestGetConsensusState() { + var ( + height exported.Height + path *ibctesting.Path + ) + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "success", func() {}, true, + }, + { + "consensus state not found", func() { + // use height with no consensus state set + height = height.(clienttypes.Height).Increment() + }, 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 := suite.chainA.App.GetIBCKeeper().ClientKeeper.MustMarshalClientState(&types.ClientState{}) + store.Set(host.ConsensusStateKey(height), clientStateBz) + }, false, + }, + { + "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 := suite.chainA.App.GetIBCKeeper().ClientKeeper.MustMarshalConsensusState(&solomachinetypes.ConsensusState{}) + store.Set(host.ConsensusStateKey(height), consensusStateBz) + }, false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupTest() + path = ibctesting.NewPath(suite.chainA, suite.chainB) + + suite.coordinator.Setup(path) + clientState := suite.chainA.GetClientState(path.EndpointA.ClientID) + height = clientState.GetLatestHeight() + + tc.malleate() // change vars as necessary + + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + consensusState, err := types.GetConsensusState(store, suite.chainA.Codec, height) + + if tc.expPass { + suite.Require().NoError(err) + expConsensusState, found := suite.chainA.GetConsensusState(path.EndpointA.ClientID, height) + suite.Require().True(found) + suite.Require().Equal(expConsensusState, consensusState) + } else { + suite.Require().Error(err) + suite.Require().Nil(consensusState) + } + }) + } +} + +func (suite *DymintTestSuite) TestGetProcessedTime() { + // setup + path := ibctesting.NewPath(suite.chainA, suite.chainB) + + suite.coordinator.UpdateTime() + // coordinator increments time before creating client + expectedTime := suite.chainA.TestChainClient.(*ibctesting.TestChainDymint).CurrentHeader.Time.Add(ibctesting.TimeIncrement) + + // Verify ProcessedTime on CreateClient + err := path.EndpointA.CreateClient() + suite.Require().NoError(err) + + clientState := suite.chainA.GetClientState(path.EndpointA.ClientID) + height := clientState.GetLatestHeight() + + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + actualTime, ok := types.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.TestChainClient.(*ibctesting.TestChainDymint).CurrentHeader.Time.Add(ibctesting.TimeIncrement) + + // Verify ProcessedTime on UpdateClient + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + clientState = suite.chainA.GetClientState(path.EndpointA.ClientID) + height = clientState.GetLatestHeight() + + store = suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + actualTime, ok = types.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 = types.GetProcessedTime(store, clienttypes.NewHeight(1, 1)) + suite.Require().False(ok, "retrieved processed time for a non-existent consensus state") +} + +func (suite *DymintTestSuite) 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 := types.IterationKey(h) + retrievedHeight := types.GetHeightFromIterationKey(k) + suite.Require().Equal(h, retrievedHeight, "retrieving height from iteration key failed") + } +} + +func (suite *DymintTestSuite) TestIterateConsensusStates() { + nextValsHash := []byte("nextVals") + + // Set iteration keys and consensus states + types.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), types.NewConsensusState(time.Now(), commitmenttypes.NewMerkleRoot([]byte("hash0-1")), nextValsHash)) + types.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), types.NewConsensusState(time.Now(), commitmenttypes.NewMerkleRoot([]byte("hash4-9")), nextValsHash)) + types.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), types.NewConsensusState(time.Now(), commitmenttypes.NewMerkleRoot([]byte("hash0-10")), nextValsHash)) + types.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), types.NewConsensusState(time.Now(), commitmenttypes.NewMerkleRoot([]byte("hash0-4")), nextValsHash)) + types.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), types.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 + } + + types.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 *DymintTestSuite) TestGetNeighboringConsensusStates() { + nextValsHash := []byte("nextVals") + cs01 := types.NewConsensusState(time.Now().UTC(), commitmenttypes.NewMerkleRoot([]byte("hash0-1")), nextValsHash) + cs04 := types.NewConsensusState(time.Now().UTC(), commitmenttypes.NewMerkleRoot([]byte("hash0-4")), nextValsHash) + cs49 := types.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 + types.SetIterationKey(suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), "testClient"), height01) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), "testClient", height01, cs01) + types.SetIterationKey(suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), "testClient"), height04) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), "testClient", height04, cs04) + types.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 := types.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 := types.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 := types.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 := types.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/01-dymint/types/update.go b/modules/light-clients/01-dymint/types/update.go new file mode 100644 index 00000000000..033bd114050 --- /dev/null +++ b/modules/light-clients/01-dymint/types/update.go @@ -0,0 +1,263 @@ +package types + +import ( + "bytes" + "reflect" + "time" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/tendermint/tendermint/light" + tmtypes "github.com/tendermint/tendermint/types" + + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" +) + +// CheckHeaderAndUpdateState checks if the provided header is valid, and if valid it will: +// create the consensus state for the header.Height +// and update the client state if the header height is greater than the latest client state height +// It returns an error if: +// - the client or header provided are not parseable to dymint 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 +// +// UpdateClient 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 +// UpdateClient 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 +// Dymint client validity checking uses the bisection algorithm described +// in the [Tendermint spec](https://github.com/tendermint/spec/blob/master/spec/consensus/light-client.md). +// +// Misbehaviour Detection: +// UpdateClient will detect implicit misbehaviour by enforcing certain invariants on any new update call and will return a frozen client. +// 1. Any valid update that creates a different consensus state for an already existing height is evidence of misbehaviour and will freeze client. +// 2. Any valid update that breaks time monotonicity with respect to its neighboring consensus states is evidence of misbehaviour and will freeze client. +// Misbehaviour sets frozen height to {0, 1} since it is only used as a boolean value (zero or non-zero). +// +// Pruning: +// UpdateClient will additionally 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) CheckHeaderAndUpdateState( + ctx sdk.Context, cdc codec.BinaryCodec, clientStore sdk.KVStore, + header exported.Header, +) (exported.ClientState, exported.ConsensusState, error) { + tmHeader, ok := header.(*Header) + if !ok { + return nil, nil, sdkerrors.Wrapf( + clienttypes.ErrInvalidHeader, "expected type %T, got %T", &Header{}, header, + ) + } + + // 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. + var conflictingHeader bool + prevConsState, _ := GetConsensusState(clientStore, cdc, header.GetHeight()) + if prevConsState != nil { + // 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(prevConsState, tmHeader.ConsensusState()) { + return &cs, prevConsState, nil + } + // A consensus state already exists for this height, but it does not match the provided header. + // Thus, we must check that this header is valid, and if so we will freeze the client. + conflictingHeader = true + } + + // get consensus state from clientStore + trustedConsState, err := GetConsensusState(clientStore, cdc, tmHeader.TrustedHeight) + if err != nil { + return nil, nil, sdkerrors.Wrapf( + err, "could not get consensus state from clientstore at TrustedHeight: %s", tmHeader.TrustedHeight, + ) + } + + if err := checkValidity(&cs, trustedConsState, tmHeader, ctx.BlockTime()); err != nil { + return nil, nil, err + } + + consState := tmHeader.ConsensusState() + // Header is different from existing consensus state and also valid, so freeze the client and return + if conflictingHeader { + cs.FrozenHeight = FrozenHeight + return &cs, consState, nil + } + // Check that consensus state timestamps are monotonic + prevCons, prevOk := GetPreviousConsensusState(clientStore, cdc, header.GetHeight()) + nextCons, nextOk := GetNextConsensusState(clientStore, cdc, header.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, freeze the client and return. + if prevOk && !prevCons.Timestamp.Before(consState.Timestamp) { + cs.FrozenHeight = FrozenHeight + return &cs, consState, nil + } + // 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, freeze the client and return. + if nextOk && !nextCons.Timestamp.After(consState.Timestamp) { + cs.FrozenHeight = FrozenHeight + return &cs, consState, nil + } + + // 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 + pruneError error + ) + pruneCb := func(height exported.Height) bool { + consState, err := GetConsensusState(clientStore, cdc, height) + // this error should never occur + if err != nil { + pruneError = err + return true + } + if cs.IsExpired(consState.Timestamp, ctx.BlockTime()) { + pruneHeight = height + } + return true + } + IterateConsensusStateAscending(clientStore, pruneCb) + if pruneError != nil { + return nil, nil, pruneError + } + // if pruneHeight is set, delete consensus state and metadata + if pruneHeight != nil { + deleteConsensusState(clientStore, pruneHeight) + deleteConsensusMetadata(clientStore, pruneHeight) + } + + newClientState, consensusState := update(ctx, clientStore, &cs, tmHeader) + return newClientState, consensusState, nil +} + +// checkTrustedHeader checks that consensus state matches trusted fields of Header +func checkTrustedHeader(header *Header, consState *ConsensusState) error { + tmTrustedValidators, err := tmtypes.ValidatorSetFromProto(header.TrustedValidators) + if err != nil { + return sdkerrors.Wrap(err, "trusted validator set in not dymint 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 sdkerrors.Wrapf( + ErrInvalidValidatorSet, + "trusted validators %s, does not hash to latest trusted validators. Expected: %X, got: %X", + header.TrustedValidators, consState.NextValidatorsHash, tvalHash, + ) + } + return nil +} + +// checkValidity checks if the Dymint header is valid. +// CONTRACT: consState.Height == header.TrustedHeight +func checkValidity( + clientState *ClientState, consState *ConsensusState, + header *Header, currentTimestamp time.Time, +) error { + 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 sdkerrors.Wrapf( + ErrInvalidHeaderHeight, + "header height revision %d does not match trusted header revision %d", + header.GetHeight().GetRevisionNumber(), header.TrustedHeight.RevisionNumber, + ) + } + + tmTrustedValidators, err := tmtypes.ValidatorSetFromProto(header.TrustedValidators) + if err != nil { + return sdkerrors.Wrap(err, "trusted validator set in not dymint validator set type") + } + + tmSignedHeader, err := tmtypes.SignedHeaderFromProto(header.SignedHeader) + if err != nil { + return sdkerrors.Wrap(err, "signed header in not dymint signed header type") + } + + tmValidatorSet, err := tmtypes.ValidatorSetFromProto(header.ValidatorSet) + if err != nil { + return sdkerrors.Wrap(err, "validator set in not dymint validator set type") + } + + // assert header height is newer than consensus state + if header.GetHeight().LTE(header.TrustedHeight) { + return sdkerrors.Wrapf( + clienttypes.ErrInvalidHeader, + "header height ≤ consensus state height (%s ≤ %s)", header.GetHeight(), header.TrustedHeight, + ) + } + + chainID := clientState.GetChainID() + // If chainID is in revision format, then set revision number of chainID with the revision number + // of the header we are verifying + // This is useful if the update is at a previous revision rather than an update to the latest revision + // of the client. + // The chainID must be set correctly for the previous revision before attempting verification. + // Updates for previous revisions are not supported if the chainID is not in revision format. + if clienttypes.IsRevisionFormat(chainID) { + chainID, _ = clienttypes.SetRevisionNumber(chainID, header.GetHeight().GetRevisionNumber()) + } + + // Construct a trusted header using the fields in consensus state + // Only Height, Time, and NextValidatorsHash are necessary for verification + trustedHeader := tmtypes.Header{ + ChainID: chainID, + Height: int64(header.TrustedHeight.RevisionHeight), + Time: consState.Timestamp, + NextValidatorsHash: consState.NextValidatorsHash, + } + signedHeader := tmtypes.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, + clientState.TrustingPeriod, currentTimestamp, clientState.MaxClockDrift, clientState.TrustLevel.ToDymint(), + ) + if err != nil { + return sdkerrors.Wrap(err, "failed to verify header") + } + return nil +} + +// update the consensus state from a new header and set processed time metadata +func update(ctx sdk.Context, clientStore sdk.KVStore, clientState *ClientState, header *Header) (*ClientState, *ConsensusState) { + height := header.GetHeight().(clienttypes.Height) + if height.GT(clientState.LatestHeight) { + clientState.LatestHeight = height + } + consensusState := &ConsensusState{ + Timestamp: header.GetTime(), + Root: commitmenttypes.NewMerkleRoot(header.Header.GetAppHash()), + NextValidatorsHash: header.Header.NextValidatorsHash, + } + + // set metadata for this consensus state + setConsensusMetadata(ctx, clientStore, header.GetHeight()) + + return clientState, consensusState +} diff --git a/modules/light-clients/01-dymint/types/update_test.go b/modules/light-clients/01-dymint/types/update_test.go new file mode 100644 index 00000000000..1c50fda14c3 --- /dev/null +++ b/modules/light-clients/01-dymint/types/update_test.go @@ -0,0 +1,458 @@ +package types_test + +import ( + "fmt" + "time" + + tmtypes "github.com/tendermint/tendermint/types" + + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" + types "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" + ibctesting "github.com/cosmos/ibc-go/v3/testing" + ibctestingmock "github.com/cosmos/ibc-go/v3/testing/mock" +) + +func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { + var ( + clientState *types.ClientState + consensusState *types.ConsensusState + consStateHeight clienttypes.Height + newHeader *types.Header + currentTime time.Time + bothValSet *tmtypes.ValidatorSet + signers []tmtypes.PrivValidator + bothSigners []tmtypes.PrivValidator + ) + + // Setup different validators and signers for testing different types of updates + altPrivVal := ibctestingmock.NewPV() + altPubKey, err := altPrivVal.GetPubKey() + suite.Require().NoError(err) + + revisionHeight := int64(height.RevisionHeight) + + // create modified heights to use for test-cases + heightPlus1 := clienttypes.NewHeight(height.RevisionNumber, height.RevisionHeight+1) + heightMinus1 := clienttypes.NewHeight(height.RevisionNumber, height.RevisionHeight-1) + heightMinus3 := clienttypes.NewHeight(height.RevisionNumber, height.RevisionHeight-3) + heightPlus5 := clienttypes.NewHeight(height.RevisionNumber, height.RevisionHeight+5) + + altVal := tmtypes.NewValidator(altPubKey, revisionHeight) + // Create alternative validator set with only altVal, invalid update (too much change in valSet) + altValSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{altVal}) + altSigners := []tmtypes.PrivValidator{altPrivVal} + + chainADymint := suite.chainA.TestChainClient.(*ibctesting.TestChainDymint) + + testCases := []struct { + name string + setup func(*DymintTestSuite) + expFrozen bool + expPass bool + }{ + { + name: "successful update with next height and same validator set", + setup: func(suite *DymintTestSuite) { + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) + newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) + currentTime = suite.now + }, + expFrozen: false, + expPass: true, + }, + { + name: "successful update with future height and different validator set", + setup: func(suite *DymintTestSuite) { + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) + newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus5.RevisionHeight), height, suite.headerTime, bothValSet, suite.valSet, bothSigners) + currentTime = suite.now + }, + expFrozen: false, + expPass: true, + }, + { + name: "successful update with next height and different validator set", + setup: func(suite *DymintTestSuite) { + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), bothValSet.Hash()) + newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, bothValSet, bothValSet, bothSigners) + currentTime = suite.now + }, + expFrozen: false, + expPass: true, + }, + { + name: "successful update for a previous height", + setup: func(suite *DymintTestSuite) { + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) + consStateHeight = heightMinus3 + newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightMinus1.RevisionHeight), heightMinus3, suite.headerTime, bothValSet, suite.valSet, bothSigners) + currentTime = suite.now + }, + expFrozen: false, + expPass: true, + }, + { + name: "successful update for a previous revision", + setup: func(suite *DymintTestSuite) { + clientState = types.NewClientState(chainIDRevision1, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) + consStateHeight = heightMinus3 + newHeader = chainADymint.CreateDMClientHeader(chainIDRevision0, int64(height.RevisionHeight), heightMinus3, suite.headerTime, bothValSet, suite.valSet, bothSigners) + currentTime = suite.now + }, + expPass: true, + }, + { + name: "successful update with identical header to a previous update", + setup: func(suite *DymintTestSuite) { + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, heightPlus1, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) + newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) + currentTime = suite.now + ctx := suite.chainA.GetContext().WithBlockTime(currentTime) + // Store the header's consensus state in client store before UpdateClient call + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(ctx, clientID, heightPlus1, newHeader.ConsensusState()) + }, + expFrozen: false, + expPass: true, + }, + { + name: "misbehaviour detection: header conflicts with existing consensus state", + setup: func(suite *DymintTestSuite) { + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, heightPlus1, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) + newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) + currentTime = suite.now + ctx := suite.chainA.GetContext().WithBlockTime(currentTime) + // Change the consensus state of header and store in client store to create a conflict + conflictConsState := newHeader.ConsensusState() + conflictConsState.Root = commitmenttypes.NewMerkleRoot([]byte("conflicting apphash")) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(ctx, clientID, heightPlus1, conflictConsState) + }, + expFrozen: true, + expPass: true, + }, + { + name: "misbehaviour detection: previous consensus state time is not before header time. time monotonicity violation", + setup: func(suite *DymintTestSuite) { + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + // create an intermediate consensus state with the same time as the newHeader to create a time violation. + // header time is after client time + consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) + newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus5.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) + currentTime = suite.now + prevConsensusState := types.NewConsensusState(suite.headerTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) + ctx := suite.chainA.GetContext().WithBlockTime(currentTime) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(ctx, clientID, heightPlus1, prevConsensusState) + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, clientID) + types.SetIterationKey(clientStore, heightPlus1) + }, + expFrozen: true, + expPass: true, + }, + { + name: "misbehaviour detection: next consensus state time is not after header time. time monotonicity violation", + setup: func(suite *DymintTestSuite) { + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + // create the next consensus state with the same time as the intermediate newHeader to create a time violation. + // header time is after clientTime + consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) + newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) + currentTime = suite.now + nextConsensusState := types.NewConsensusState(suite.headerTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) + ctx := suite.chainA.GetContext().WithBlockTime(currentTime) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(ctx, clientID, heightPlus5, nextConsensusState) + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, clientID) + types.SetIterationKey(clientStore, heightPlus5) + }, + expFrozen: true, + expPass: true, + }, + { + name: "unsuccessful update with incorrect header chain-id", + setup: func(suite *DymintTestSuite) { + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) + newHeader = chainADymint.CreateDMClientHeader("ethermint", int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) + currentTime = suite.now + }, + expFrozen: false, + expPass: false, + }, + { + name: "unsuccessful update to a future revision", + setup: func(suite *DymintTestSuite) { + clientState = types.NewClientState(chainIDRevision0, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) + newHeader = chainADymint.CreateDMClientHeader(chainIDRevision1, 1, height, suite.headerTime, suite.valSet, suite.valSet, signers) + currentTime = suite.now + }, + expPass: false, + }, + { + name: "unsuccessful update: header height revision and trusted height revision mismatch", + setup: func(suite *DymintTestSuite) { + clientState = types.NewClientState(chainIDRevision1, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) + newHeader = chainADymint.CreateDMClientHeader(chainIDRevision1, 3, height, suite.headerTime, suite.valSet, suite.valSet, signers) + currentTime = suite.now + }, + expFrozen: false, + expPass: false, + }, + { + name: "unsuccessful update with next height: update header mismatches nextValSetHash", + setup: func(suite *DymintTestSuite) { + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) + newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, bothValSet, suite.valSet, bothSigners) + currentTime = suite.now + }, + expFrozen: false, + expPass: false, + }, + { + name: "unsuccessful update with next height: update header mismatches different nextValSetHash", + setup: func(suite *DymintTestSuite) { + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), bothValSet.Hash()) + newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, bothValSet, signers) + currentTime = suite.now + }, + expFrozen: false, + expPass: false, + }, + { + name: "unsuccessful update with future height: too much change in validator set", + setup: func(suite *DymintTestSuite) { + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) + newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus5.RevisionHeight), height, suite.headerTime, altValSet, suite.valSet, altSigners) + currentTime = suite.now + }, + expFrozen: false, + expPass: false, + }, + { + name: "unsuccessful updates, passed in incorrect trusted validators for given consensus state", + setup: func(suite *DymintTestSuite) { + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) + newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus5.RevisionHeight), height, suite.headerTime, bothValSet, bothValSet, bothSigners) + currentTime = suite.now + }, + expFrozen: false, + expPass: false, + }, + { + name: "unsuccessful update: trusting period has passed since last client timestamp", + setup: func(suite *DymintTestSuite) { + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) + newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) + // make current time pass trusting period from last timestamp on clientstate + currentTime = suite.now.Add(trustingPeriod) + }, + expFrozen: false, + expPass: false, + }, + { + name: "unsuccessful update: header timestamp is past current timestamp", + setup: func(suite *DymintTestSuite) { + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) + newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers) + currentTime = suite.now + }, + expFrozen: false, + expPass: false, + }, + { + name: "unsuccessful update: header timestamp is not past last client timestamp", + setup: func(suite *DymintTestSuite) { + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) + newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.clientTime, suite.valSet, suite.valSet, signers) + currentTime = suite.now + }, + expFrozen: false, + expPass: false, + }, + { + name: "header basic validation failed", + setup: func(suite *DymintTestSuite) { + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) + newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) + // cause new header to fail validatebasic by changing commit height to mismatch header height + newHeader.SignedHeader.Commit.Height = revisionHeight - 1 + currentTime = suite.now + }, + expFrozen: false, + expPass: false, + }, + { + name: "header height < consensus height", + setup: func(suite *DymintTestSuite) { + clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.NewHeight(height.RevisionNumber, heightPlus5.RevisionHeight), commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) + // Make new header at height less than latest client state + newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightMinus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) + currentTime = suite.now + }, + expFrozen: false, + expPass: false, + }, + } + + for i, tc := range testCases { + tc := tc + suite.Run(fmt.Sprintf("Case: %s", tc.name), func() { + suite.SetupTest() // reset metadata writes + // Create bothValSet with both suite validator and altVal. Would be valid update + bothValSet = tmtypes.NewValidatorSet(append(suite.valSet.Validators, altVal)) + signers = []tmtypes.PrivValidator{suite.privVal} + + // Create signer array and ensure it is in same order as bothValSet + _, suiteVal := suite.valSet.GetByIndex(0) + bothSigners = ibctesting.CreateSortedSignerArray(altPrivVal, suite.privVal, altVal, suiteVal) + + consStateHeight = height // must be explicitly changed + // setup test + tc.setup(suite) + + // Set current timestamp in context + ctx := suite.chainA.GetContext().WithBlockTime(currentTime) + + // Set trusted consensus state in client store + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(ctx, clientID, consStateHeight, consensusState) + + height := newHeader.GetHeight() + expectedConsensus := &types.ConsensusState{ + Timestamp: newHeader.GetTime(), + Root: commitmenttypes.NewMerkleRoot(newHeader.Header.GetAppHash()), + NextValidatorsHash: newHeader.Header.NextValidatorsHash, + } + + newClientState, consensusState, err := clientState.CheckHeaderAndUpdateState( + ctx, + suite.cdc, + suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), clientID), // pass in clientID prefixed clientStore + newHeader, + ) + + if tc.expPass { + suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) + + suite.Require().Equal(tc.expFrozen, !newClientState.(*types.ClientState).FrozenHeight.IsZero(), "client state status is unexpected after update") + + // further writes only happen if update is not misbehaviour + if !tc.expFrozen { + // Determine if clientState should be updated or not + // TODO: check the entire Height struct once GetLatestHeight returns clienttypes.Height + if height.GT(clientState.LatestHeight) { + // Header Height is greater than clientState latest Height, clientState should be updated with header.GetHeight() + suite.Require().Equal(height, newClientState.GetLatestHeight(), "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.GetLatestHeight(), "client state height updated for past header") + } + + suite.Require().Equal(expectedConsensus, consensusState, "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().Nil(newClientState, "invalid test case %d passed: %s", i, tc.name) + suite.Require().Nil(consensusState, "invalid test case %d passed: %s", i, tc.name) + } + }) + } +} + +func (suite *DymintTestSuite) TestPruneConsensusState() { + // create path and setup clients + path := ibctesting.NewPath(suite.chainA, suite.chainB) + suite.coordinator.SetupClients(path) + + // 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) + err := types.IterateConsensusStateAscending(clientStore, getFirstHeightCb) + suite.Require().Nil(err) + + // this height will be expired but not pruned + path.EndpointA.UpdateClient() + expiredHeight := path.EndpointA.GetClientState().GetLatestHeight() + + // 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 := types.GetProcessedTime(clientStore, expiredHeight) + suite.Require().True(ok) + expectedProcessHeight, ok := types.GetProcessedHeight(clientStore, expiredHeight) + suite.Require().True(ok) + expectedConsKey := types.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 + path.EndpointA.UpdateClient() + + // 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) + path.EndpointA.UpdateClient() + + 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 := types.GetProcessedTime(clientStore, pruneHeight) + suite.Require().Equal(uint64(0), processTime, "processed time metadata not pruned") + suite.Require().False(ok) + processHeight, ok := types.GetProcessedHeight(clientStore, pruneHeight) + suite.Require().Nil(processHeight, "processed height metadata not pruned") + suite.Require().False(ok) + + // check iteration key metadata is pruned + consKey := types.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 = types.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 = types.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 = types.GetIterationKey(clientStore, expiredHeight) + suite.Require().Equal(expectedConsKey, consKey, "iteration key incorrectly pruned") +} diff --git a/modules/light-clients/01-dymint/types/upgrade.go b/modules/light-clients/01-dymint/types/upgrade.go new file mode 100644 index 00000000000..bcc3966cfa4 --- /dev/null +++ b/modules/light-clients/01-dymint/types/upgrade.go @@ -0,0 +1,156 @@ +package types + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" +) + +// VerifyUpgradeAndUpdateState checks if the upgraded client has been committed by the current client +// It will zero out all client-specific fields (e.g. TrustingPeriod and verify all data +// in client state that must be the same across all valid Dymint clients for the new chain. +// VerifyUpgrade will return an error if: +// - the upgradedClient is not a Dymint ClientState +// - the lastest 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 Dymint 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 sdk.KVStore, + upgradedClient exported.ClientState, upgradedConsState exported.ConsensusState, + proofUpgradeClient, proofUpgradeConsState []byte, +) (exported.ClientState, exported.ConsensusState, error) { + if len(cs.UpgradePath) == 0 { + return nil, nil, sdkerrors.Wrap(clienttypes.ErrInvalidUpgradeClient, "cannot upgrade client, no upgrade path set") + } + + // last height of current counterparty chain must be client's latest height + lastHeight := cs.GetLatestHeight() + + if !upgradedClient.GetLatestHeight().GT(lastHeight) { + return nil, nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidHeight, "upgraded client height %s must be at greater than current client height %s", + upgradedClient.GetLatestHeight(), lastHeight) + } + + // upgraded client state and consensus state must be IBC dymint client state and consensus state + // this may be modified in the future to upgrade to a new IBC dymint 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 nil, nil, sdkerrors.Wrapf(clienttypes.ErrInvalidClientType, "upgraded client must be Dymint client. expected: %T got: %T", + &ClientState{}, upgradedClient) + } + tmUpgradeConsState, ok := upgradedConsState.(*ConsensusState) + if !ok { + return nil, nil, sdkerrors.Wrapf(clienttypes.ErrInvalidConsensus, "upgraded consensus state must be Dymint consensus state. expected %T, got: %T", + &ConsensusState{}, upgradedConsState) + } + + // unmarshal proofs + var merkleProofClient, merkleProofConsState commitmenttypes.MerkleProof + if err := cdc.Unmarshal(proofUpgradeClient, &merkleProofClient); err != nil { + return nil, nil, sdkerrors.Wrapf(commitmenttypes.ErrInvalidProof, "could not unmarshal client merkle proof: %v", err) + } + if err := cdc.Unmarshal(proofUpgradeConsState, &merkleProofConsState); err != nil { + return nil, nil, sdkerrors.Wrapf(commitmenttypes.ErrInvalidProof, "could not unmarshal consensus state merkle proof: %v", err) + } + + // 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, err := GetConsensusState(clientStore, cdc, lastHeight) + if err != nil { + return nil, nil, sdkerrors.Wrap(err, "could not retrieve consensus state for lastHeight") + } + + // Verify client proof + bz, err := cdc.MarshalInterface(upgradedClient) + if err != nil { + return nil, nil, sdkerrors.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 nil, nil, sdkerrors.Wrapf(err, "client state proof failed. Path: %s", upgradeClientPath.Pretty()) + } + + // Verify consensus state proof + bz, err = cdc.MarshalInterface(upgradedConsState) + if err != nil { + return nil, nil, sdkerrors.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 nil, nil, sdkerrors.Wrapf(err, "consensus state proof failed. Path: %s", upgradeConsStatePath.Pretty()) + } + + // 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, cs.TrustingPeriod, tmUpgradeClient.UnbondingPeriod, + cs.MaxClockDrift, tmUpgradeClient.LatestHeight, tmUpgradeClient.ProofSpecs, tmUpgradeClient.UpgradePath, + cs.AllowUpdateAfterExpiry, cs.AllowUpdateAfterMisbehaviour, + ) + + if err := newClientState.Validate(); err != nil { + return nil, nil, sdkerrors.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, + ) + + // set metadata for this consensus state + setConsensusMetadata(ctx, clientStore, tmUpgradeClient.LatestHeight) + + return newClientState, newConsState, nil +} + +// construct MerklePath for the committed client from upgradePath +func constructUpgradeClientMerklePath(upgradePath []string, lastHeight exported.Height) commitmenttypes.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) + return commitmenttypes.NewMerklePath(clientPath...) +} + +// construct MerklePath for the committed consensus state from upgradePath +func constructUpgradeConsStateMerklePath(upgradePath []string, lastHeight exported.Height) commitmenttypes.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) + return commitmenttypes.NewMerklePath(consPath...) +} diff --git a/modules/light-clients/01-dymint/types/upgrade_test.go b/modules/light-clients/01-dymint/types/upgrade_test.go new file mode 100644 index 00000000000..e8463550531 --- /dev/null +++ b/modules/light-clients/01-dymint/types/upgrade_test.go @@ -0,0 +1,482 @@ +package types_test + +import ( + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" + "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" + ibctesting "github.com/cosmos/ibc-go/v3/testing" +) + +var ( + newChainId = "newChainId-1" +) + +func (suite *DymintTestSuite) TestVerifyUpgrade() { + var ( + upgradedClient exported.ClientState + upgradedConsState exported.ConsensusState + lastHeight clienttypes.Height + path *ibctesting.Path + proofUpgradedClient, proofUpgradedConsState []byte + upgradedClientBz, upgradedConsStateBz []byte + err error + ) + + testCases := []struct { + name string + setup func() + expPass bool + }{ + { + 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) + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) + + // 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) + + proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + }, + expPass: true, + }, + { + name: "successful upgrade to same revision", + setup: func() { + upgradedHeight := clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+2)) + // don't use -1 suffix in chain id + upgradedClient = types.NewClientState("newChainId", types.DefaultTrustLevel, trustingPeriod, ubdPeriod+trustingPeriod, maxClockDrift, upgradedHeight, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + upgradedClient = upgradedClient.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) + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) + + // 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) + + proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + }, + expPass: true, + }, + + { + 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) + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) + + // 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) + + proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + }, + expPass: false, + }, + { + name: "unsuccessful upgrade: committed client does not have zeroed custom fields", + setup: func() { + // non-zeroed upgrade client + upgradedClient = types.NewClientState(newChainId, types.DefaultTrustLevel, trustingPeriod, ubdPeriod+trustingPeriod, maxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + 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) + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) + + // 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) + + proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + }, + expPass: false, + }, + { + 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 + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) + + // change upgradedClient client-specified parameters + upgradedClient = types.NewClientState("wrongchainID", types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), upgradePath, true, true) + + 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) + + proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + }, + expPass: false, + }, + { + name: "unsuccessful upgrade: client-specified parameters do not match previous client", + setup: func() { + // zero custom fields and store in upgrade store + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) + + // change upgradedClient client-specified parameters + upgradedClient = types.NewClientState(newChainId, types.DefaultTrustLevel, ubdPeriod, ubdPeriod+trustingPeriod, maxClockDrift+5, lastHeight, commitmenttypes.GetSDKSpecs(), upgradePath, true, false) + + 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) + + proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + }, + expPass: false, + }, + { + name: "unsuccessful upgrade: relayer-submitted consensus state does not match counterparty-committed consensus state", + 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) + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) + + // change submitted upgradedConsensusState + upgradedConsState = &types.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) + + proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + }, + expPass: false, + }, + { + name: "unsuccessful upgrade: client proof unmarshal failed", + setup: func() { + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) + + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + + proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + + proofUpgradedClient = []byte("proof") + }, + expPass: false, + }, + { + name: "unsuccessful upgrade: consensus state proof unmarshal failed", + setup: func() { + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) + + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + + proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + + proofUpgradedConsState = []byte("proof") + }, + expPass: false, + }, + { + 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) + + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + + proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + }, + expPass: false, + }, + { + 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) + + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + + proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + }, + expPass: false, + }, + { + name: "unsuccessful upgrade: upgrade 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) + + // 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) + + proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + + // SetClientState with empty upgrade path + tmClient, _ := cs.(*types.ClientState) + tmClient.UpgradePath = []string{""} + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID, tmClient) + }, + expPass: false, + }, + { + 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) + + // 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) + + proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + }, + expPass: false, + }, + { + 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) + + // 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) + + proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + }, + expPass: false, + }, + { + 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) + + // 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) + + proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + }, + expPass: false, + }, + { + 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) + + // 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) + + proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + }, + expPass: false, + }, + { + 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 = types.NewClientState(newChainId, types.DefaultTrustLevel, trustingPeriod, trustingPeriod, maxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + 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) + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) + + // 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) + + proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + }, + expPass: false, + }, + } + + for _, tc := range testCases { + tc := tc + + // reset suite + suite.SetupTest() + path = ibctesting.NewPath(suite.chainA, suite.chainB) + + suite.coordinator.SetupClients(path) + upgradedClient = types.NewClientState(newChainId, types.DefaultTrustLevel, trustingPeriod, ubdPeriod+trustingPeriod, maxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + upgradedClient = upgradedClient.ZeroCustomFields() + upgradedClientBz, err = clienttypes.MarshalClientState(suite.chainA.App.AppCodec(), upgradedClient) + suite.Require().NoError(err) + + upgradedConsState = &types.ConsensusState{ + NextValidatorsHash: []byte("nextValsHash"), + } + upgradedConsStateBz, err = clienttypes.MarshalConsensusState(suite.chainA.App.AppCodec(), upgradedConsState) + suite.Require().NoError(err) + + tc.setup() + + cs := suite.chainA.GetClientState(path.EndpointA.ClientID) + 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 + upgradedClient = upgradedClient.ZeroCustomFields() + + clientState, consensusState, err := cs.VerifyUpgradeAndUpdateState( + suite.chainA.GetContext(), + suite.cdc, + clientStore, + upgradedClient, + upgradedConsState, + proofUpgradedClient, + proofUpgradedConsState, + ) + + if tc.expPass { + suite.Require().NoError(err, "verify upgrade failed on valid case: %s", tc.name) + suite.Require().NotNil(clientState, "verify upgrade failed on valid case: %s", tc.name) + suite.Require().NotNil(consensusState, "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().Nil(clientState, "verify upgrade passed on invalid case: %s", tc.name) + + suite.Require().Nil(consensusState, "verify upgrade passed on invalid case: %s", tc.name) + + } + } +} diff --git a/proto/ibc/lightclients/dymint/dymint.proto b/proto/ibc/lightclients/dymint/dymint.proto new file mode 100644 index 00000000000..c0f5fc9f727 --- /dev/null +++ b/proto/ibc/lightclients/dymint/dymint.proto @@ -0,0 +1,115 @@ +syntax = "proto3"; + +package ibc.lightclients.dymint.v1; + +option go_package = "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types"; + +import "tendermint/types/validator.proto"; +import "tendermint/types/types.proto"; +import "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 Dymint 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, (gogoproto.moretags) = "yaml:\"trust_level\""]; + // duration of the period since the LastestTimestamp during which the + // submitted headers are valid for upgrade + google.protobuf.Duration trusting_period = 3 + [(gogoproto.nullable) = false, (gogoproto.stdduration) = true, (gogoproto.moretags) = "yaml:\"trusting_period\""]; + // duration of the staking unbonding period + google.protobuf.Duration unbonding_period = 4 [ + (gogoproto.nullable) = false, + (gogoproto.stdduration) = true, + (gogoproto.moretags) = "yaml:\"unbonding_period\"" + ]; + // 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, (gogoproto.moretags) = "yaml:\"max_clock_drift\""]; + // Block height when the client was frozen due to a misbehaviour + ibc.core.client.v1.Height frozen_height = 6 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"frozen_height\""]; + // Latest height the client was updated to + ibc.core.client.v1.Height latest_height = 7 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"latest_height\""]; + + // Proof specifications used in verifying counterparty state + repeated ics23.ProofSpec proof_specs = 8 [(gogoproto.moretags) = "yaml:\"proof_specs\""]; + + // 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 [(gogoproto.moretags) = "yaml:\"upgrade_path\""]; + + // This flag, when set to true, will allow governance to recover a client + // which has expired + bool allow_update_after_expiry = 10 [(gogoproto.moretags) = "yaml:\"allow_update_after_expiry\""]; + // This flag, when set to true, will allow governance to unfreeze a client + // whose chain has experienced a misbehaviour event + bool allow_update_after_misbehaviour = 11 [(gogoproto.moretags) = "yaml:\"allow_update_after_misbehaviour\""]; +} + +// ConsensusState defines the consensus state from Dymint. +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/tendermint/tendermint/libs/bytes.HexBytes", + (gogoproto.moretags) = "yaml:\"next_validators_hash\"" + ]; +} + +// Misbehaviour is a wrapper over two conflicting Headers +// that implements Misbehaviour interface expected by ICS-02 +message Misbehaviour { + option (gogoproto.goproto_getters) = false; + + string client_id = 1 [(gogoproto.moretags) = "yaml:\"client_id\""]; + Header header_1 = 2 [(gogoproto.customname) = "Header1", (gogoproto.moretags) = "yaml:\"header_1\""]; + Header header_2 = 3 [(gogoproto.customname) = "Header2", (gogoproto.moretags) = "yaml:\"header_2\""]; +} + +// Header defines the Dymint client consensus Header. +// It encapsulates all the information necessary to update from a trusted +// Dymint 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, (gogoproto.moretags) = "yaml:\"signed_header\""]; + + .tendermint.types.ValidatorSet validator_set = 2 [(gogoproto.moretags) = "yaml:\"validator_set\""]; + ibc.core.client.v1.Height trusted_height = 3 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"trusted_height\""]; + .tendermint.types.ValidatorSet trusted_validators = 4 [(gogoproto.moretags) = "yaml:\"trusted_validators\""]; +} + +// 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/testing/chain.go b/testing/chain.go index 0a66039d57d..e711cd6c463 100644 --- a/testing/chain.go +++ b/testing/chain.go @@ -53,6 +53,8 @@ func NewTestChainClient(chain *TestChain, chainConsensusType string) TestChainCl switch chainConsensusType { case exported.Tendermint: return NewChainTendermintClient(chain) + case exported.Dymint: + return NewChainDymintClient(chain) default: panic(fmt.Sprintf("client type %s is not supported", chainConsensusType)) } diff --git a/testing/chain_dymint.go b/testing/chain_dymint.go new file mode 100644 index 00000000000..cef0c20f04c --- /dev/null +++ b/testing/chain_dymint.go @@ -0,0 +1,259 @@ +package ibctesting + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/tmhash" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + tmprotoversion "github.com/tendermint/tendermint/proto/tendermint/version" + tmtypes "github.com/tendermint/tendermint/types" + tmversion "github.com/tendermint/tendermint/version" + + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" + ibcdmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" +) + +var ( + // Default params variables used to create a DM client + DefaultDymintTrustLevel ibcdmtypes.Fraction = ibcdmtypes.DefaultTrustLevel +) + +type DymintConfig struct { + TrustLevel ibcdmtypes.Fraction + TrustingPeriod time.Duration + UnbondingPeriod time.Duration + MaxClockDrift time.Duration + AllowUpdateAfterExpiry bool + AllowUpdateAfterMisbehaviour bool +} + +func (tmcfg *DymintConfig) GetClientType() string { + return exported.Dymint +} + +var _ ClientConfig = &DymintConfig{} + +// TestChainDymint is a testing struct that 'wraps' a TestChain with the last DM Header, +// the current ABCI header. +type TestChainDymint struct { + TC *TestChain + + LastHeader *ibcdmtypes.Header // header for last block height committed + CurrentHeader tmproto.Header // header for current block height + +} + +var _ TestChainClientI = &TestChainDymint{} + +// NewChainDymintClient initializes the consunsus spesisifc pare of the TestChain +func NewChainDymintClient(tc *TestChain) *TestChainDymint { + + // create current header and call begin block + header := tmproto.Header{ + ChainID: tc.ChainID, + Height: 1, + Time: tc.Coordinator.CurrentTime.UTC(), + } + + // create an account to send transactions from + chain := &TestChainDymint{ + tc, + nil, + header, + } + + return chain +} + +func (chain *TestChainDymint) NewConfig() ClientConfig { + return &DymintConfig{ + TrustLevel: DefaultDymintTrustLevel, + TrustingPeriod: TrustingPeriod, + UnbondingPeriod: UnbondingPeriod, + MaxClockDrift: MaxClockDrift, + AllowUpdateAfterExpiry: false, + AllowUpdateAfterMisbehaviour: false, + } +} + +// GetContext returns the current context for the application. +func (chain *TestChainDymint) GetContext() sdk.Context { + return chain.TC.App.GetBaseApp().NewContext(false, chain.CurrentHeader) +} + +// 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. +// +// CONTRACT: this function must only be called after app.Commit() occurs +func (chain *TestChainDymint) NextBlock() { + // set the last header to the current header + // use nil trusted fields + chain.LastHeader = chain.CurrentDMClientHeader() + + // increment the current header + chain.CurrentHeader = tmproto.Header{ + ChainID: chain.TC.ChainID, + Height: chain.TC.App.LastBlockHeight() + 1, + AppHash: chain.TC.App.LastCommitID().Hash, + // NOTE: the time is increased by the coordinator to maintain time synchrony amongst + // chains. + Time: chain.CurrentHeader.Time, + ValidatorsHash: chain.TC.Vals.Hash(), + NextValidatorsHash: chain.TC.Vals.Hash(), + } + + chain.BeginBlock() +} + +// ConstructUpdateClientHeader will construct a valid 01-dymint Header to update the +// light client on the source chain. +func (chain *TestChainDymint) ConstructUpdateClientHeader(counterparty *TestChain, clientID string) (exported.Header, error) { + return chain.ConstructUpdateDMClientHeader(counterparty, clientID) +} + +// ConstructUpdateDMClientHeader will construct a valid 01-dymint Header to update the +// light client on the source chain. +func (chain *TestChainDymint) ConstructUpdateDMClientHeader(counterparty *TestChain, clientID string) (*ibcdmtypes.Header, error) { + return chain.ConstructUpdateDMClientHeaderWithTrustedHeight(counterparty, clientID, clienttypes.ZeroHeight()) +} + +// ConstructUpdateDMClientHeader will construct a valid 01-dymint Header to update the +// light client on the source chain. +func (chain *TestChainDymint) ConstructUpdateDMClientHeaderWithTrustedHeight(counterparty *TestChain, clientID string, trustedHeight clienttypes.Height) (*ibcdmtypes.Header, error) { + counterpartyTestChainDymint := counterparty.TestChainClient.(*TestChainDymint) + header := counterpartyTestChainDymint.LastHeader + // Relayer must query for LatestHeight on client to get TrustedHeight if the trusted height is not set + if trustedHeight.IsZero() { + trustedHeight = chain.TC.GetClientState(clientID).GetLatestHeight().(clienttypes.Height) + } + var ( + tmTrustedVals *tmtypes.ValidatorSet + ok bool + ) + // Once we get TrustedHeight from client, we must query the validators from the counterparty chain + // If the LatestHeight == LastHeader.Height, then TrustedValidators are current validators + // If LatestHeight < LastHeader.Height, we can query the historical validator set from HistoricalInfo + if trustedHeight == counterpartyTestChainDymint.LastHeader.GetHeight() { + tmTrustedVals = counterparty.Vals + } else { + // NOTE: We need to get validators from counterparty at height: trustedHeight+1 + // since the last trusted validators for a header at height h + // is the NextValidators at h+1 committed to in header h by + // NextValidatorsHash + tmTrustedVals, ok = counterparty.GetValsAtHeight(int64(trustedHeight.RevisionHeight + 1)) + if !ok { + return nil, sdkerrors.Wrapf(ibcdmtypes.ErrInvalidHeaderHeight, "could not retrieve trusted validators at trustedHeight: %d", trustedHeight) + } + } + // inject trusted fields into last header + // for now assume revision number is 0 + header.TrustedHeight = trustedHeight + + trustedVals, err := tmTrustedVals.ToProto() + if err != nil { + return nil, err + } + header.TrustedValidators = trustedVals + + return header, nil +} + +// CurrentDMClientHeader creates a DM header using the current header parameters +// on the chain. The trusted fields in the header are set to nil. +func (chain *TestChainDymint) CurrentDMClientHeader() *ibcdmtypes.Header { + return chain.CreateDMClientHeader(chain.TC.ChainID, chain.CurrentHeader.Height, clienttypes.Height{}, chain.CurrentHeader.Time, chain.TC.Vals, nil, chain.TC.Signers) +} + +// CreateDMClientHeader creates a DM header to update the DM client. Args are passed in to allow +// caller flexibility to use params that differ from the chain. +func (chain *TestChainDymint) CreateDMClientHeader(chainID string, blockHeight int64, trustedHeight clienttypes.Height, timestamp time.Time, tmValSet, tmTrustedVals *tmtypes.ValidatorSet, signers []tmtypes.PrivValidator) *ibcdmtypes.Header { + var ( + valSet *tmproto.ValidatorSet + trustedVals *tmproto.ValidatorSet + ) + require.NotNil(chain.TC.T, tmValSet) + + vsetHash := tmValSet.Hash() + + tmHeader := tmtypes.Header{ + Version: tmprotoversion.Consensus{Block: tmversion.BlockProtocol, App: 2}, + ChainID: chainID, + Height: blockHeight, + Time: timestamp, + LastBlockID: MakeBlockID(make([]byte, tmhash.Size), 10_000, make([]byte, tmhash.Size)), + LastCommitHash: chain.TC.App.LastCommitID().Hash, + DataHash: tmhash.Sum([]byte("data_hash")), + ValidatorsHash: vsetHash, + NextValidatorsHash: vsetHash, + ConsensusHash: tmhash.Sum([]byte("consensus_hash")), + AppHash: chain.CurrentHeader.AppHash, + LastResultsHash: tmhash.Sum([]byte("last_results_hash")), + EvidenceHash: tmhash.Sum([]byte("evidence_hash")), + ProposerAddress: tmValSet.Proposer.Address, //nolint:staticcheck + } + + hhash := tmHeader.Hash() + blockID := MakeBlockID(hhash, 3, tmhash.Sum([]byte("part_set"))) + voteSet := tmtypes.NewVoteSet(chainID, blockHeight, 1, tmproto.PrecommitType, tmValSet) + + commit, err := tmtypes.MakeCommit(blockID, blockHeight, 1, voteSet, signers, timestamp) + require.NoError(chain.TC.T, err) + + signedHeader := &tmproto.SignedHeader{ + Header: tmHeader.ToProto(), + Commit: commit.ToProto(), + } + + if tmValSet != nil { + valSet, err = tmValSet.ToProto() + require.NoError(chain.TC.T, err) + } + + if tmTrustedVals != nil { + trustedVals, err = tmTrustedVals.ToProto() + require.NoError(chain.TC.T, err) + } + + // 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 &ibcdmtypes.Header{ + SignedHeader: signedHeader, + ValidatorSet: valSet, + TrustedHeight: trustedHeight, + TrustedValidators: trustedVals, + } +} + +// UpdateTimeForChain updates the clock for this chain. +func (chain *TestChainDymint) UpdateCurrentHeaderTime(t time.Time) { + chain.CurrentHeader.Time = t +} + +// BeginBlock signals the beginning of a block with chain.CurrentHeader +func (chain *TestChainDymint) BeginBlock() { + chain.TC.App.BeginBlock(abci.RequestBeginBlock{Header: chain.CurrentHeader}) +} + +// ClientConfigToState builds the ClientState based on the clientConfig and last header +func (chain *TestChainDymint) ClientConfigToState(clientConfig ClientConfig) exported.ClientState { + tmConfig, ok := clientConfig.(*DymintConfig) + require.True(chain.TC.T, ok) + + height := chain.LastHeader.GetHeight().(clienttypes.Height) + clientState := ibcdmtypes.NewClientState( + chain.TC.ChainID, tmConfig.TrustLevel, tmConfig.TrustingPeriod, tmConfig.UnbondingPeriod, tmConfig.MaxClockDrift, + height, commitmenttypes.GetSDKSpecs(), UpgradePath, tmConfig.AllowUpdateAfterExpiry, tmConfig.AllowUpdateAfterMisbehaviour, + ) + return clientState +} + +// GetConsensusState returns the consensus state of the last header +func (chain *TestChainDymint) GetConsensusState() exported.ConsensusState { + return chain.LastHeader.ConsensusState() +} diff --git a/testing/simapp/app.go b/testing/simapp/app.go index bbfbbe7690f..47b817323e0 100644 --- a/testing/simapp/app.go +++ b/testing/simapp/app.go @@ -100,6 +100,7 @@ import ( porttypes "github.com/cosmos/ibc-go/v3/modules/core/05-port/types" ibchost "github.com/cosmos/ibc-go/v3/modules/core/24-host" ibckeeper "github.com/cosmos/ibc-go/v3/modules/core/keeper" + ibcdmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" ibcmock "github.com/cosmos/ibc-go/v3/testing/mock" @@ -340,6 +341,8 @@ func NewSimAppWithConsensusType( switch chainConsensusType { case exported.Tendermint: selfClient = ibctmtypes.NewSelfClient() + case exported.Dymint: + selfClient = ibcdmtypes.NewSelfClient() default: panic(fmt.Sprintf("client type %s is not supported", chainConsensusType)) } From 99cf0cbe570504a3b3c045e86c5cd839c53c8400 Mon Sep 17 00:00:00 2001 From: Lior Zilpa Date: Tue, 22 Nov 2022 14:49:35 +0200 Subject: [PATCH 111/140] move ConstructUpdateClientHeader to common testChain code using a new interface GetSelfClientType --- modules/core/02-client/keeper/client_test.go | 3 +-- testing/chain.go | 15 +++++++++++++- testing/chain_tendermint.go | 21 ++++++++++---------- testing/endpoint.go | 2 +- 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/modules/core/02-client/keeper/client_test.go b/modules/core/02-client/keeper/client_test.go index dd37764f08b..e21f11130bb 100644 --- a/modules/core/02-client/keeper/client_test.go +++ b/modules/core/02-client/keeper/client_test.go @@ -49,8 +49,7 @@ func (suite *KeeperTestSuite) TestUpdateClientTendermint() { // Must create header creation functions since suite.header gets recreated on each test case createFutureUpdateFn := func(trustedHeight clienttypes.Height) *ibctmtypes.Header { - chainATendermint := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint) - header, err := chainATendermint.ConstructUpdateTMClientHeaderWithTrustedHeight(path.EndpointB.Chain, path.EndpointA.ClientID, trustedHeight) + header, err := ibctesting.ConstructUpdateTMClientHeaderWithTrustedHeight(path.EndpointB.Chain, path.EndpointA.ClientID, trustedHeight) suite.Require().NoError(err) return header } diff --git a/testing/chain.go b/testing/chain.go index 0a66039d57d..2b02b71eb8e 100644 --- a/testing/chain.go +++ b/testing/chain.go @@ -44,7 +44,7 @@ type TestChainClientI interface { ClientConfigToState(ClientConfig ClientConfig) exported.ClientState GetConsensusState() exported.ConsensusState NewConfig() ClientConfig - ConstructUpdateClientHeader(counterparty *TestChain, clientID string) (exported.Header, error) + GetSelfClientType() string } func NewTestChainClient(chain *TestChain, chainConsensusType string) TestChainClientI { @@ -427,3 +427,16 @@ func (chain *TestChain) GetChannelCapability(portID, channelID string) *capabili return cap } + +// ConstructUpdateClientHeader will construct a valid 01-dymint Header to update the +// light client on the source chain. +func (chain *TestChain) ConstructUpdateClientHeader(counterparty *TestChain, clientID string) (exported.Header, error) { + // Relayer must query for LatestHeight on client to get TrustedHeight if the trusted height is not set + trustedHeight := chain.GetClientState(clientID).GetLatestHeight().(clienttypes.Height) + switch counterparty.TestChainClient.GetSelfClientType() { + case exported.Tendermint: + return ConstructUpdateTMClientHeaderWithTrustedHeight(counterparty, clientID, trustedHeight) + default: + panic(fmt.Sprintf("client type %s is not supported", counterparty.TestChainClient.GetSelfClientType())) + } +} diff --git a/testing/chain_tendermint.go b/testing/chain_tendermint.go index a5a09b78f5c..24903ff7eeb 100644 --- a/testing/chain_tendermint.go +++ b/testing/chain_tendermint.go @@ -72,6 +72,10 @@ func NewChainTendermintClient(tc *TestChain) *TestChainTendermint { return chain } +func (chain *TestChainTendermint) GetSelfClientType() string { + return exported.Tendermint +} + func (chain *TestChainTendermint) NewConfig() ClientConfig { return &TendermintConfig{ TrustLevel: DefaultTrustLevel, @@ -112,27 +116,22 @@ func (chain *TestChainTendermint) NextBlock() { chain.BeginBlock() } -// ConstructUpdateClientHeader will construct a valid 07-tendermint Header to update the -// light client on the source chain. -func (chain *TestChainTendermint) ConstructUpdateClientHeader(counterparty *TestChain, clientID string) (exported.Header, error) { - return chain.ConstructUpdateTMClientHeader(counterparty, clientID) -} - // ConstructUpdateTMClientHeader will construct a valid 07-tendermint Header to update the // light client on the source chain. func (chain *TestChainTendermint) ConstructUpdateTMClientHeader(counterparty *TestChain, clientID string) (*ibctmtypes.Header, error) { - return chain.ConstructUpdateTMClientHeaderWithTrustedHeight(counterparty, clientID, clienttypes.ZeroHeight()) + // Relayer must query for LatestHeight on client to get TrustedHeight if the trusted height is not set + trustedHeight := chain.TC.GetClientState(clientID).GetLatestHeight().(clienttypes.Height) + return ConstructUpdateTMClientHeaderWithTrustedHeight(counterparty, clientID, trustedHeight) } // ConstructUpdateTMClientHeader will construct a valid 07-tendermint Header to update the // light client on the source chain. -func (chain *TestChainTendermint) ConstructUpdateTMClientHeaderWithTrustedHeight(counterparty *TestChain, clientID string, trustedHeight clienttypes.Height) (*ibctmtypes.Header, error) { +func ConstructUpdateTMClientHeaderWithTrustedHeight(counterparty *TestChain, clientID string, trustedHeight clienttypes.Height) (*ibctmtypes.Header, error) { counterpartyTestChainTendermint := counterparty.TestChainClient.(*TestChainTendermint) header := counterpartyTestChainTendermint.LastHeader // Relayer must query for LatestHeight on client to get TrustedHeight if the trusted height is not set - if trustedHeight.IsZero() { - trustedHeight = chain.TC.GetClientState(clientID).GetLatestHeight().(clienttypes.Height) - } + require.False(counterparty.T, trustedHeight.IsZero()) + var ( tmTrustedVals *tmtypes.ValidatorSet ok bool diff --git a/testing/endpoint.go b/testing/endpoint.go index 9ac2658cadb..90ce3217d18 100644 --- a/testing/endpoint.go +++ b/testing/endpoint.go @@ -106,7 +106,7 @@ func (endpoint *Endpoint) UpdateClient() (err error) { // ensure counterparty has committed state endpoint.Chain.Coordinator.CommitBlock(endpoint.Counterparty.Chain) - header, err := endpoint.Chain.TestChainClient.ConstructUpdateClientHeader(endpoint.Counterparty.Chain, endpoint.ClientID) + header, err := endpoint.Chain.ConstructUpdateClientHeader(endpoint.Counterparty.Chain, endpoint.ClientID) if err != nil { return err From f8b752d73495bb122856baaa6a4122953d479484 Mon Sep 17 00:00:00 2001 From: Lior Zilpa Date: Tue, 22 Nov 2022 16:36:17 +0200 Subject: [PATCH 112/140] fix CreateClient to use the correct ClientConfig --- testing/endpoint.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/endpoint.go b/testing/endpoint.go index 90ce3217d18..908d7f73f76 100644 --- a/testing/endpoint.go +++ b/testing/endpoint.go @@ -78,7 +78,7 @@ func (endpoint *Endpoint) CreateClient() (err error) { // ensure counterparty has committed state endpoint.Chain.Coordinator.CommitBlock(endpoint.Counterparty.Chain) - clientState := endpoint.Counterparty.Chain.TestChainClient.ClientConfigToState(endpoint.ClientConfig) + clientState := endpoint.Counterparty.Chain.TestChainClient.ClientConfigToState(endpoint.Counterparty.ClientConfig) consensusState := endpoint.Counterparty.Chain.TestChainClient.GetConsensusState() if err != nil { From 95151b964cd4eb7325532a1b67af3ac1a392b8e5 Mon Sep 17 00:00:00 2001 From: Lior Zilpa Date: Tue, 22 Nov 2022 16:37:36 +0200 Subject: [PATCH 113/140] 07-tendermint assumes the other chain is also tendermint - fix it --- .../07-tendermint/types/client_state.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/light-clients/07-tendermint/types/client_state.go b/modules/light-clients/07-tendermint/types/client_state.go index 51f826979fd..90a49af4fe0 100644 --- a/modules/light-clients/07-tendermint/types/client_state.go +++ b/modules/light-clients/07-tendermint/types/client_state.go @@ -216,10 +216,10 @@ func (cs ClientState) VerifyClientState( return sdkerrors.Wrap(clienttypes.ErrInvalidClient, "client state cannot be empty") } - _, ok := clientState.(*ClientState) - if !ok { - return sdkerrors.Wrapf(clienttypes.ErrInvalidClient, "invalid client type %T, expected %T", clientState, &ClientState{}) - } + // _, ok := clientState.(*ClientState) + // if !ok { + // return sdkerrors.Wrapf(clienttypes.ErrInvalidClient, "invalid client type %T, expected %T", clientState, &ClientState{}) + // } bz, err := cdc.MarshalInterface(clientState) if err != nil { @@ -256,10 +256,10 @@ func (cs ClientState) VerifyClientConsensusState( return sdkerrors.Wrap(clienttypes.ErrInvalidConsensus, "consensus state cannot be empty") } - _, ok := consensusState.(*ConsensusState) - if !ok { - return sdkerrors.Wrapf(clienttypes.ErrInvalidConsensus, "invalid consensus type %T, expected %T", consensusState, &ConsensusState{}) - } + // _, ok := consensusState.(*ConsensusState) + // if !ok { + // return sdkerrors.Wrapf(clienttypes.ErrInvalidConsensus, "invalid consensus type %T, expected %T", consensusState, &ConsensusState{}) + // } bz, err := cdc.MarshalInterface(consensusState) if err != nil { From 07debb3e063f1cc8611693b98658ea4a886f1630 Mon Sep 17 00:00:00 2001 From: Lior Zilpa Date: Tue, 22 Nov 2022 16:59:43 +0200 Subject: [PATCH 114/140] Support dymint<->tendermint IBC tests --- .../01-dymint/types/client_state.go | 16 +- .../01-dymint/types/client_state_test.go | 306 ++++++++++++------ .../01-dymint/types/dymint_test.go | 47 ++- .../01-dymint/types/header_test.go | 38 ++- .../types/misbehaviour_handle_test.go | 111 ++++--- .../01-dymint/types/misbehaviour_test.go | 42 ++- .../01-dymint/types/proposal_handle_test.go | 153 +++++++-- .../01-dymint/types/store_test.go | 136 +++++--- .../01-dymint/types/update_test.go | 6 +- .../01-dymint/types/upgrade_test.go | 261 ++++++++------- testing/chain.go | 2 + testing/chain_dymint.go | 23 +- 12 files changed, 739 insertions(+), 402 deletions(-) diff --git a/modules/light-clients/01-dymint/types/client_state.go b/modules/light-clients/01-dymint/types/client_state.go index 92087f0ed6d..37cbaa7a1aa 100644 --- a/modules/light-clients/01-dymint/types/client_state.go +++ b/modules/light-clients/01-dymint/types/client_state.go @@ -216,10 +216,10 @@ func (cs ClientState) VerifyClientState( return sdkerrors.Wrap(clienttypes.ErrInvalidClient, "client state cannot be empty") } - _, ok := clientState.(*ClientState) - if !ok { - return sdkerrors.Wrapf(clienttypes.ErrInvalidClient, "invalid client type %T, expected %T", clientState, &ClientState{}) - } + // _, ok := clientState.(*ClientState) + // if !ok { + // return sdkerrors.Wrapf(clienttypes.ErrInvalidClient, "invalid client type %T, expected %T", clientState, &ClientState{}) + // } bz, err := cdc.MarshalInterface(clientState) if err != nil { @@ -256,10 +256,10 @@ func (cs ClientState) VerifyClientConsensusState( return sdkerrors.Wrap(clienttypes.ErrInvalidConsensus, "consensus state cannot be empty") } - _, ok := consensusState.(*ConsensusState) - if !ok { - return sdkerrors.Wrapf(clienttypes.ErrInvalidConsensus, "invalid consensus type %T, expected %T", consensusState, &ConsensusState{}) - } + // _, ok := consensusState.(*ConsensusState) + // if !ok { + // return sdkerrors.Wrapf(clienttypes.ErrInvalidConsensus, "invalid consensus type %T, expected %T", consensusState, &ConsensusState{}) + // } bz, err := cdc.MarshalInterface(consensusState) if err != nil { diff --git a/modules/light-clients/01-dymint/types/client_state_test.go b/modules/light-clients/01-dymint/types/client_state_test.go index 0fc6bba9e6a..f85e3b1f1a6 100644 --- a/modules/light-clients/01-dymint/types/client_state_test.go +++ b/modules/light-clients/01-dymint/types/client_state_test.go @@ -33,8 +33,10 @@ var ( func (suite *DymintTestSuite) TestStatus() { var ( - path *ibctesting.Path - clientState *types.ClientState + path *ibctesting.Path + clientState *types.ClientState + dymintCounterpartyChain *ibctesting.TestChain + endpoint *ibctesting.Endpoint ) testCases := []struct { @@ -45,11 +47,11 @@ func (suite *DymintTestSuite) TestStatus() { {"client is active", func() {}, exported.Active}, {"client is frozen", func() { clientState.FrozenHeight = clienttypes.NewHeight(0, 1) - path.EndpointA.SetClientState(clientState) + endpoint.SetClientState(clientState) }, exported.Frozen}, {"client status without consensus state", func() { clientState.LatestHeight = clientState.LatestHeight.Increment().(clienttypes.Height) - path.EndpointA.SetClientState(clientState) + endpoint.SetClientState(clientState) }, exported.Expired}, {"client status is expired", func() { suite.coordinator.IncrementTimeBy(clientState.TrustingPeriod) @@ -60,12 +62,22 @@ func (suite *DymintTestSuite) TestStatus() { path = ibctesting.NewPath(suite.chainA, suite.chainB) suite.coordinator.SetupClients(path) - clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) - clientState = path.EndpointA.GetClientState().(*types.ClientState) + if suite.chainB.TestChainClient.GetSelfClientType() == exported.Tendermint { + // chainA must be Dymint + dymintCounterpartyChain = suite.chainB + endpoint = path.EndpointB + } else { + // chainB must be Dymint + dymintCounterpartyChain = suite.chainA + endpoint = path.EndpointA + } + + clientStore := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.ClientStore(dymintCounterpartyChain.GetContext(), endpoint.ClientID) + clientState = endpoint.GetClientState().(*types.ClientState) tc.malleate() - status := clientState.Status(suite.chainA.GetContext(), clientStore, suite.chainA.App.AppCodec()) + status := clientState.Status(dymintCounterpartyChain.GetContext(), clientStore, dymintCounterpartyChain.App.AppCodec()) suite.Require().Equal(tc.expStatus, status) } @@ -166,7 +178,10 @@ func (suite *DymintTestSuite) TestValidate() { } func (suite *DymintTestSuite) TestInitialize() { - + var ( + dymintCounterpartyChain *ibctesting.TestChain + endpoint *ibctesting.Endpoint + ) testCases := []struct { name string consensusState exported.ConsensusState @@ -185,14 +200,23 @@ func (suite *DymintTestSuite) TestInitialize() { } path := ibctesting.NewPath(suite.chainA, suite.chainB) - err := path.EndpointA.CreateClient() + if suite.chainB.TestChainClient.GetSelfClientType() == exported.Tendermint { + // chainA must be Dymint + dymintCounterpartyChain = suite.chainB + endpoint = path.EndpointB + } else { + // chainB must be Dymint + dymintCounterpartyChain = suite.chainA + endpoint = path.EndpointA + } + err := endpoint.CreateClient() suite.Require().NoError(err) - clientState := suite.chainA.GetClientState(path.EndpointA.ClientID) - store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + clientState := dymintCounterpartyChain.GetClientState(endpoint.ClientID) + store := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.ClientStore(dymintCounterpartyChain.GetContext(), endpoint.ClientID) for _, tc := range testCases { - err := clientState.Initialize(suite.chainA.GetContext(), suite.chainA.Codec, store, tc.consensusState) + err := clientState.Initialize(dymintCounterpartyChain.GetContext(), dymintCounterpartyChain.Codec, store, tc.consensusState) if tc.expPass { suite.Require().NoError(err, "valid case returned an error") } else { @@ -270,10 +294,12 @@ func (suite *DymintTestSuite) TestVerifyClientConsensusState() { // light client on chainA func (suite *DymintTestSuite) TestVerifyConnectionState() { var ( - clientState *types.ClientState - proof []byte - proofHeight exported.Height - prefix commitmenttypes.MerklePrefix + clientState *types.ClientState + proof []byte + proofHeight exported.Height + prefix commitmenttypes.MerklePrefix + dymintChain, dymintCounterpartyChain *ibctesting.TestChain + endpoint1, endpoint2 *ibctesting.Endpoint ) testCases := []struct { @@ -305,30 +331,43 @@ func (suite *DymintTestSuite) TestVerifyConnectionState() { tc := tc suite.Run(tc.name, func() { - suite.SetupTest() // reset + suite.SetupTestWithConsensusType(exported.Tendermint, exported.Dymint) // reset // setup testing conditions path := ibctesting.NewPath(suite.chainA, suite.chainB) suite.coordinator.Setup(path) - connection := path.EndpointB.GetConnection() + + if suite.chainB.TestChainClient.GetSelfClientType() == exported.Dymint { + dymintCounterpartyChain = suite.chainA + dymintChain = suite.chainB + endpoint1 = path.EndpointA + endpoint2 = path.EndpointB + } else { + dymintCounterpartyChain = suite.chainB + dymintChain = suite.chainA + endpoint1 = path.EndpointB + endpoint2 = path.EndpointA + } + + connection := endpoint2.GetConnection() var ok bool - clientStateI := suite.chainA.GetClientState(path.EndpointA.ClientID) + clientStateI := dymintCounterpartyChain.GetClientState(endpoint1.ClientID) clientState, ok = clientStateI.(*types.ClientState) suite.Require().True(ok) - prefix = suite.chainB.GetPrefix() + prefix = dymintChain.GetPrefix() // make connection proof - connectionKey := host.ConnectionKey(path.EndpointB.ConnectionID) - proof, proofHeight = suite.chainB.QueryProof(connectionKey) + connectionKey := host.ConnectionKey(endpoint2.ConnectionID) + proof, proofHeight = dymintChain.QueryProof(connectionKey) tc.malleate() // make changes as necessary - store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + store := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.ClientStore(dymintCounterpartyChain.GetContext(), endpoint1.ClientID) err := clientState.VerifyConnectionState( - store, suite.chainA.Codec, proofHeight, &prefix, proof, path.EndpointB.ConnectionID, connection, + store, dymintCounterpartyChain.Codec, proofHeight, &prefix, proof, endpoint2.ConnectionID, connection, ) if tc.expPass { @@ -344,10 +383,12 @@ func (suite *DymintTestSuite) TestVerifyConnectionState() { // client on chainA func (suite *DymintTestSuite) TestVerifyChannelState() { var ( - clientState *types.ClientState - proof []byte - proofHeight exported.Height - prefix commitmenttypes.MerklePrefix + clientState *types.ClientState + proof []byte + proofHeight exported.Height + prefix commitmenttypes.MerklePrefix + dymintChain, dymintCounterpartyChain *ibctesting.TestChain + endpoint1, endpoint2 *ibctesting.Endpoint ) testCases := []struct { @@ -379,31 +420,44 @@ func (suite *DymintTestSuite) TestVerifyChannelState() { tc := tc suite.Run(tc.name, func() { - suite.SetupTest() // reset + suite.SetupTestWithConsensusType(exported.Tendermint, exported.Dymint) // reset // setup testing conditions path := ibctesting.NewPath(suite.chainA, suite.chainB) suite.coordinator.Setup(path) - channel := path.EndpointB.GetChannel() + + if suite.chainB.TestChainClient.GetSelfClientType() == exported.Dymint { + dymintCounterpartyChain = suite.chainA + dymintChain = suite.chainB + endpoint1 = path.EndpointA + endpoint2 = path.EndpointB + } else { + dymintCounterpartyChain = suite.chainB + dymintChain = suite.chainA + endpoint1 = path.EndpointB + endpoint2 = path.EndpointA + } + + channel := endpoint2.GetChannel() var ok bool - clientStateI := suite.chainA.GetClientState(path.EndpointA.ClientID) + clientStateI := dymintCounterpartyChain.GetClientState(endpoint1.ClientID) clientState, ok = clientStateI.(*types.ClientState) suite.Require().True(ok) - prefix = suite.chainB.GetPrefix() + prefix = dymintChain.GetPrefix() // make channel proof - channelKey := host.ChannelKey(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) - proof, proofHeight = suite.chainB.QueryProof(channelKey) + channelKey := host.ChannelKey(endpoint2.ChannelConfig.PortID, endpoint2.ChannelID) + proof, proofHeight = dymintChain.QueryProof(channelKey) tc.malleate() // make changes as necessary - store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + store := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.ClientStore(dymintCounterpartyChain.GetContext(), endpoint1.ClientID) err := clientState.VerifyChannelState( - store, suite.chainA.Codec, proofHeight, &prefix, proof, - path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, channel, + store, dymintCounterpartyChain.Codec, proofHeight, &prefix, proof, + endpoint2.ChannelConfig.PortID, endpoint2.ChannelID, channel, ) if tc.expPass { @@ -419,12 +473,14 @@ func (suite *DymintTestSuite) TestVerifyChannelState() { // in the light client on chainA. A send from chainB to chainA is simulated. func (suite *DymintTestSuite) TestVerifyPacketCommitment() { var ( - clientState *types.ClientState - proof []byte - delayTimePeriod uint64 - delayBlockPeriod uint64 - proofHeight exported.Height - prefix commitmenttypes.MerklePrefix + clientState *types.ClientState + proof []byte + delayTimePeriod uint64 + delayBlockPeriod uint64 + proofHeight exported.Height + prefix commitmenttypes.MerklePrefix + dymintChain, dymintCounterpartyChain *ibctesting.TestChain + endpoint1, endpoint2 *ibctesting.Endpoint ) testCases := []struct { @@ -490,32 +546,45 @@ func (suite *DymintTestSuite) TestVerifyPacketCommitment() { // setup testing conditions path := ibctesting.NewPath(suite.chainA, suite.chainB) suite.coordinator.Setup(path) - packet := channeltypes.NewPacket(ibctesting.MockPacketData, 1, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, clienttypes.NewHeight(0, 100), 0) - err := path.EndpointB.SendPacket(packet) + + if suite.chainB.TestChainClient.GetSelfClientType() == exported.Dymint { + dymintCounterpartyChain = suite.chainA + dymintChain = suite.chainB + endpoint1 = path.EndpointA + endpoint2 = path.EndpointB + } else { + dymintCounterpartyChain = suite.chainB + dymintChain = suite.chainA + endpoint1 = path.EndpointB + endpoint2 = path.EndpointA + } + + packet := channeltypes.NewPacket(ibctesting.MockPacketData, 1, endpoint2.ChannelConfig.PortID, endpoint2.ChannelID, endpoint1.ChannelConfig.PortID, endpoint1.ChannelID, clienttypes.NewHeight(0, 100), 0) + err := endpoint2.SendPacket(packet) suite.Require().NoError(err) var ok bool - clientStateI := suite.chainA.GetClientState(path.EndpointA.ClientID) + clientStateI := dymintCounterpartyChain.GetClientState(endpoint1.ClientID) clientState, ok = clientStateI.(*types.ClientState) suite.Require().True(ok) - prefix = suite.chainB.GetPrefix() + prefix = dymintChain.GetPrefix() // make packet commitment proof packetKey := host.PacketCommitmentKey(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) - proof, proofHeight = path.EndpointB.QueryProof(packetKey) + proof, proofHeight = endpoint2.QueryProof(packetKey) // reset time and block delays to 0, malleate may change to a specific non-zero value. delayTimePeriod = 0 delayBlockPeriod = 0 tc.malleate() // make changes as necessary - ctx := suite.chainA.GetContext() - store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, path.EndpointA.ClientID) + ctx := dymintCounterpartyChain.GetContext() + store := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, endpoint1.ClientID) - commitment := channeltypes.CommitPacket(suite.chainA.App.GetIBCKeeper().Codec(), packet) + commitment := channeltypes.CommitPacket(dymintCounterpartyChain.App.GetIBCKeeper().Codec(), packet) err = clientState.VerifyPacketCommitment( - ctx, store, suite.chainA.Codec, proofHeight, delayTimePeriod, delayBlockPeriod, &prefix, proof, + ctx, store, dymintCounterpartyChain.Codec, proofHeight, delayTimePeriod, delayBlockPeriod, &prefix, proof, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence(), commitment, ) @@ -533,12 +602,14 @@ func (suite *DymintTestSuite) TestVerifyPacketCommitment() { // is simulated. func (suite *DymintTestSuite) TestVerifyPacketAcknowledgement() { var ( - clientState *types.ClientState - proof []byte - delayTimePeriod uint64 - delayBlockPeriod uint64 - proofHeight exported.Height - prefix commitmenttypes.MerklePrefix + clientState *types.ClientState + proof []byte + delayTimePeriod uint64 + delayBlockPeriod uint64 + proofHeight exported.Height + prefix commitmenttypes.MerklePrefix + dymintCounterpartyChain, dymintChain *ibctesting.TestChain + endpoint1, endpoint2 *ibctesting.Endpoint ) testCases := []struct { @@ -604,37 +675,50 @@ func (suite *DymintTestSuite) TestVerifyPacketAcknowledgement() { // setup testing conditions path := ibctesting.NewPath(suite.chainA, suite.chainB) suite.coordinator.Setup(path) - packet := channeltypes.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.NewHeight(0, 100), 0) + + if suite.chainB.TestChainClient.GetSelfClientType() == exported.Dymint { + dymintCounterpartyChain = suite.chainA + dymintChain = suite.chainB + endpoint1 = path.EndpointA + endpoint2 = path.EndpointB + } else { + dymintCounterpartyChain = suite.chainB + dymintChain = suite.chainA + endpoint1 = path.EndpointB + endpoint2 = path.EndpointA + } + + packet := channeltypes.NewPacket(ibctesting.MockPacketData, 1, endpoint1.ChannelConfig.PortID, endpoint1.ChannelID, endpoint2.ChannelConfig.PortID, endpoint2.ChannelID, clienttypes.NewHeight(0, 100), 0) // send packet - err := path.EndpointA.SendPacket(packet) + err := endpoint1.SendPacket(packet) suite.Require().NoError(err) // write receipt and ack - err = path.EndpointB.RecvPacket(packet) + err = endpoint2.RecvPacket(packet) suite.Require().NoError(err) var ok bool - clientStateI := suite.chainA.GetClientState(path.EndpointA.ClientID) + clientStateI := dymintCounterpartyChain.GetClientState(endpoint1.ClientID) clientState, ok = clientStateI.(*types.ClientState) suite.Require().True(ok) - prefix = suite.chainB.GetPrefix() + prefix = dymintChain.GetPrefix() // make packet acknowledgement proof acknowledgementKey := host.PacketAcknowledgementKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) - proof, proofHeight = suite.chainB.QueryProof(acknowledgementKey) + proof, proofHeight = dymintChain.QueryProof(acknowledgementKey) // reset time and block delays to 0, malleate may change to a specific non-zero value. delayTimePeriod = 0 delayBlockPeriod = 0 tc.malleate() // make changes as necessary - ctx := suite.chainA.GetContext() - store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, path.EndpointA.ClientID) + ctx := dymintCounterpartyChain.GetContext() + store := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, endpoint1.ClientID) err = clientState.VerifyPacketAcknowledgement( - ctx, store, suite.chainA.Codec, proofHeight, delayTimePeriod, delayBlockPeriod, &prefix, proof, + ctx, store, dymintCounterpartyChain.Codec, proofHeight, delayTimePeriod, delayBlockPeriod, &prefix, proof, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), ibcmock.MockAcknowledgement.Acknowledgement(), ) @@ -652,12 +736,14 @@ func (suite *DymintTestSuite) TestVerifyPacketAcknowledgement() { // no receive. func (suite *DymintTestSuite) TestVerifyPacketReceiptAbsence() { var ( - clientState *types.ClientState - proof []byte - delayTimePeriod uint64 - delayBlockPeriod uint64 - proofHeight exported.Height - prefix commitmenttypes.MerklePrefix + clientState *types.ClientState + proof []byte + delayTimePeriod uint64 + delayBlockPeriod uint64 + proofHeight exported.Height + prefix commitmenttypes.MerklePrefix + dymintChain, dymintCounterpartyChain *ibctesting.TestChain + endpoint1, endpoint2 *ibctesting.Endpoint ) testCases := []struct { @@ -723,33 +809,46 @@ func (suite *DymintTestSuite) TestVerifyPacketReceiptAbsence() { // setup testing conditions path := ibctesting.NewPath(suite.chainA, suite.chainB) suite.coordinator.Setup(path) - packet := channeltypes.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.NewHeight(0, 100), 0) + + if suite.chainB.TestChainClient.GetSelfClientType() == exported.Dymint { + dymintCounterpartyChain = suite.chainA + dymintChain = suite.chainB + endpoint1 = path.EndpointA + endpoint2 = path.EndpointB + } else { + dymintCounterpartyChain = suite.chainB + dymintChain = suite.chainA + endpoint1 = path.EndpointB + endpoint2 = path.EndpointA + } + + packet := channeltypes.NewPacket(ibctesting.MockPacketData, 1, endpoint1.ChannelConfig.PortID, endpoint1.ChannelID, endpoint2.ChannelConfig.PortID, endpoint2.ChannelID, clienttypes.NewHeight(0, 100), 0) // send packet, but no recv - err := path.EndpointA.SendPacket(packet) + err := endpoint1.SendPacket(packet) suite.Require().NoError(err) var ok bool - clientStateI := suite.chainA.GetClientState(path.EndpointA.ClientID) + clientStateI := dymintCounterpartyChain.GetClientState(endpoint1.ClientID) clientState, ok = clientStateI.(*types.ClientState) suite.Require().True(ok) - prefix = suite.chainB.GetPrefix() + prefix = dymintChain.GetPrefix() // make packet receipt absence proof receiptKey := host.PacketReceiptKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) - proof, proofHeight = path.EndpointB.QueryProof(receiptKey) + proof, proofHeight = endpoint2.QueryProof(receiptKey) // reset time and block delays to 0, malleate may change to a specific non-zero value. delayTimePeriod = 0 delayBlockPeriod = 0 tc.malleate() // make changes as necessary - ctx := suite.chainA.GetContext() - store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, path.EndpointA.ClientID) + ctx := dymintCounterpartyChain.GetContext() + store := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, endpoint1.ClientID) err = clientState.VerifyPacketReceiptAbsence( - ctx, store, suite.chainA.Codec, proofHeight, delayTimePeriod, delayBlockPeriod, &prefix, proof, + ctx, store, dymintCounterpartyChain.Codec, proofHeight, delayTimePeriod, delayBlockPeriod, &prefix, proof, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), ) @@ -767,12 +866,14 @@ func (suite *DymintTestSuite) TestVerifyPacketReceiptAbsence() { // simulated. func (suite *DymintTestSuite) TestVerifyNextSeqRecv() { var ( - clientState *types.ClientState - proof []byte - delayTimePeriod uint64 - delayBlockPeriod uint64 - proofHeight exported.Height - prefix commitmenttypes.MerklePrefix + clientState *types.ClientState + proof []byte + delayTimePeriod uint64 + delayBlockPeriod uint64 + proofHeight exported.Height + prefix commitmenttypes.MerklePrefix + dymintChain, dymintCounterpartyChain *ibctesting.TestChain + endpoint1, endpoint2 *ibctesting.Endpoint ) testCases := []struct { @@ -839,37 +940,50 @@ func (suite *DymintTestSuite) TestVerifyNextSeqRecv() { path := ibctesting.NewPath(suite.chainA, suite.chainB) path.SetChannelOrdered() suite.coordinator.Setup(path) - packet := channeltypes.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.NewHeight(0, 100), 0) + + if suite.chainB.TestChainClient.GetSelfClientType() == exported.Dymint { + dymintCounterpartyChain = suite.chainA + dymintChain = suite.chainB + endpoint1 = path.EndpointA + endpoint2 = path.EndpointB + } else { + dymintCounterpartyChain = suite.chainB + dymintChain = suite.chainA + endpoint1 = path.EndpointB + endpoint2 = path.EndpointA + } + + packet := channeltypes.NewPacket(ibctesting.MockPacketData, 1, endpoint1.ChannelConfig.PortID, endpoint1.ChannelID, endpoint2.ChannelConfig.PortID, endpoint2.ChannelID, clienttypes.NewHeight(0, 100), 0) // send packet - err := path.EndpointA.SendPacket(packet) + err := endpoint1.SendPacket(packet) suite.Require().NoError(err) // next seq recv incremented - err = path.EndpointB.RecvPacket(packet) + err = endpoint2.RecvPacket(packet) suite.Require().NoError(err) var ok bool - clientStateI := suite.chainA.GetClientState(path.EndpointA.ClientID) + clientStateI := dymintCounterpartyChain.GetClientState(endpoint1.ClientID) clientState, ok = clientStateI.(*types.ClientState) suite.Require().True(ok) - prefix = suite.chainB.GetPrefix() + prefix = dymintChain.GetPrefix() // make next seq recv proof nextSeqRecvKey := host.NextSequenceRecvKey(packet.GetDestPort(), packet.GetDestChannel()) - proof, proofHeight = suite.chainB.QueryProof(nextSeqRecvKey) + proof, proofHeight = dymintChain.QueryProof(nextSeqRecvKey) // reset time and block delays to 0, malleate may change to a specific non-zero value. delayTimePeriod = 0 delayBlockPeriod = 0 tc.malleate() // make changes as necessary - ctx := suite.chainA.GetContext() - store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, path.EndpointA.ClientID) + ctx := dymintCounterpartyChain.GetContext() + store := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, endpoint1.ClientID) err = clientState.VerifyNextSequenceRecv( - ctx, store, suite.chainA.Codec, proofHeight, delayTimePeriod, delayBlockPeriod, &prefix, proof, + ctx, store, dymintCounterpartyChain.Codec, proofHeight, delayTimePeriod, delayBlockPeriod, &prefix, proof, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()+1, ) diff --git a/modules/light-clients/01-dymint/types/dymint_test.go b/modules/light-clients/01-dymint/types/dymint_test.go index b17263b3ef2..b0a01b21181 100644 --- a/modules/light-clients/01-dymint/types/dymint_test.go +++ b/modules/light-clients/01-dymint/types/dymint_test.go @@ -54,10 +54,23 @@ type DymintTestSuite struct { now time.Time headerTime time.Time clientTime time.Time + + // consensus setup + chainAConsensusType string + chainBConsensusType string } func (suite *DymintTestSuite) SetupTest() { - suite.coordinator = ibctesting.NewCoordinatorWithConsensusType(suite.T(), []string{exported.Dymint, exported.Dymint}) + //suite.SetupTestWithConsensusType(exported.Dymint, exported.Tendermint) + suite.SetupTestWithConsensusType(suite.chainAConsensusType, suite.chainBConsensusType) +} + +func (suite *DymintTestSuite) SetupTestWithConsensusType(chainAConsensusType string, chainBConsensusType string) { + suite.Require().True(chainAConsensusType == exported.Dymint || chainBConsensusType == exported.Dymint) + suite.Require().True(chainAConsensusType == exported.Dymint || chainAConsensusType == exported.Tendermint) + suite.Require().True(chainBConsensusType == exported.Dymint || chainBConsensusType == exported.Tendermint) + + suite.coordinator = ibctesting.NewCoordinatorWithConsensusType(suite.T(), []string{chainAConsensusType, chainBConsensusType}) 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) @@ -87,8 +100,15 @@ func (suite *DymintTestSuite) SetupTest() { val := tmtypes.NewValidator(pubKey, 10) suite.valSet = tmtypes.NewValidatorSet([]*tmtypes.Validator{val}) suite.valsHash = suite.valSet.Hash() - chainADymint := suite.chainA.TestChainClient.(*ibctesting.TestChainDymint) - suite.header = chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, []tmtypes.PrivValidator{suite.privVal}) + if chainAConsensusType == exported.Tendermint { + chainBDymint := suite.chainB.TestChainClient.(*ibctesting.TestChainDymint) + suite.header = chainBDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, []tmtypes.PrivValidator{suite.privVal}) + } else { + // chainA must be Dymint + chainADymint := suite.chainA.TestChainClient.(*ibctesting.TestChainDymint) + suite.header = chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, []tmtypes.PrivValidator{suite.privVal}) + } + suite.ctx = app.BaseApp.NewContext(checkTx, tmproto.Header{Height: 1, Time: suite.now}) } @@ -105,6 +125,23 @@ func getBothSigners(suite *DymintTestSuite, altVal *tmtypes.Validator, altPrivVa return bothValSet, bothSigners } -func TestDymintTestSuite(t *testing.T) { - suite.Run(t, new(DymintTestSuite)) +func TestDymintTestSuiteDymTm(t *testing.T) { + suite.Run(t, &DymintTestSuite{ + chainAConsensusType: exported.Dymint, + chainBConsensusType: exported.Tendermint, + }) +} + +func TestDymintTestSuiteTmDym(t *testing.T) { + suite.Run(t, &DymintTestSuite{ + chainAConsensusType: exported.Tendermint, + chainBConsensusType: exported.Dymint, + }) +} + +func TestDymintTestSuiteDymDym(t *testing.T) { + suite.Run(t, &DymintTestSuite{ + chainAConsensusType: exported.Dymint, + chainBConsensusType: exported.Dymint, + }) } diff --git a/modules/light-clients/01-dymint/types/header_test.go b/modules/light-clients/01-dymint/types/header_test.go index 17b0aaac708..02aea15c969 100644 --- a/modules/light-clients/01-dymint/types/header_test.go +++ b/modules/light-clients/01-dymint/types/header_test.go @@ -12,18 +12,31 @@ import ( ) func (suite *DymintTestSuite) TestGetHeight() { - header := suite.chainA.TestChainClient.(*ibctesting.TestChainDymint).LastHeader - suite.Require().NotEqual(uint64(0), header.GetHeight()) + if suite.chainA.TestChainClient.GetSelfClientType() == exported.Dymint { + header := suite.chainA.TestChainClient.(*ibctesting.TestChainDymint).LastHeader + suite.Require().NotEqual(uint64(0), header.GetHeight()) + } else { + // chainB must be Dymint + header := suite.chainB.TestChainClient.(*ibctesting.TestChainDymint).LastHeader + suite.Require().NotEqual(uint64(0), header.GetHeight()) + } } func (suite *DymintTestSuite) TestGetTime() { - header := suite.chainA.TestChainClient.(*ibctesting.TestChainDymint).LastHeader - suite.Require().NotEqual(time.Time{}, header.GetTime()) + if suite.chainA.TestChainClient.GetSelfClientType() == exported.Dymint { + header := suite.chainA.TestChainClient.(*ibctesting.TestChainDymint).LastHeader + suite.Require().NotEqual(time.Time{}, header.GetTime()) + } else { + // chainB must be Dymint + header := suite.chainB.TestChainClient.(*ibctesting.TestChainDymint).LastHeader + suite.Require().NotEqual(time.Time{}, header.GetTime()) + } } func (suite *DymintTestSuite) TestHeaderValidateBasic() { var ( - header *types.Header + header *types.Header + dymintChain *ibctesting.TestChainDymint ) testCases := []struct { name string @@ -41,7 +54,7 @@ func (suite *DymintTestSuite) TestHeaderValidateBasic() { header.SignedHeader.Commit.Height = -1 }, false}, {"signed header failed dymint ValidateBasic", func() { - header = suite.chainA.TestChainClient.(*ibctesting.TestChainDymint).LastHeader + header = dymintChain.LastHeader header.SignedHeader.Commit = nil }, false}, {"trusted height is equal to header height", func() { @@ -53,10 +66,6 @@ func (suite *DymintTestSuite) TestHeaderValidateBasic() { {"ValidatorSetFromProto failed", func() { header.ValidatorSet.Validators[0].PubKey = tmprotocrypto.PublicKey{} }, false}, - {"header validator hash does not equal hash of validator set", func() { - // use chainB's randomly generated validator set - header.ValidatorSet = suite.chainB.TestChainClient.(*ibctesting.TestChainDymint).LastHeader.ValidatorSet - }, false}, } suite.Require().Equal(exported.Dymint, suite.header.ClientType()) @@ -67,7 +76,14 @@ func (suite *DymintTestSuite) TestHeaderValidateBasic() { suite.Run(tc.name, func() { suite.SetupTest() - header = suite.chainA.TestChainClient.(*ibctesting.TestChainDymint).LastHeader // must be explicitly changed in malleate + if suite.chainA.TestChainClient.GetSelfClientType() == exported.Dymint { + dymintChain = suite.chainA.TestChainClient.(*ibctesting.TestChainDymint) + } else { + // chainB must be Dymint + dymintChain = suite.chainB.TestChainClient.(*ibctesting.TestChainDymint) + } + + header = dymintChain.LastHeader // must be explicitly changed in malleate tc.malleate() diff --git a/modules/light-clients/01-dymint/types/misbehaviour_handle_test.go b/modules/light-clients/01-dymint/types/misbehaviour_handle_test.go index f450b6d7f8b..7b2ad331acd 100644 --- a/modules/light-clients/01-dymint/types/misbehaviour_handle_test.go +++ b/modules/light-clients/01-dymint/types/misbehaviour_handle_test.go @@ -16,6 +16,7 @@ import ( ) func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { + var chainDymint *ibctesting.TestChainDymint altPrivVal := ibctestingmock.NewPV() altPubKey, err := altPrivVal.GetPubKey() suite.Require().NoError(err) @@ -38,7 +39,11 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { heightMinus1 := clienttypes.NewHeight(height.RevisionNumber, height.RevisionHeight-1) heightMinus3 := clienttypes.NewHeight(height.RevisionNumber, height.RevisionHeight-3) - chainADymint := suite.chainA.TestChainClient.(*ibctesting.TestChainDymint) + if suite.chainA.TestChainClient.GetSelfClientType() == exported.Dymint { + chainDymint = suite.chainA.TestChainClient.(*ibctesting.TestChainDymint) + } else { + chainDymint = suite.chainB.TestChainClient.(*ibctesting.TestChainDymint) + } testCases := []struct { name string @@ -59,8 +64,8 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), - Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ClientId: chainID, }, suite.now, @@ -74,8 +79,8 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+3), height, suite.now, bothValSet, bothValSet, bothSigners), - Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), + Header1: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+3), height, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), ClientId: chainID, }, suite.now, @@ -89,8 +94,8 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+3), height, suite.now, bothValSet, bothValSet, bothSigners), - Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Hour), bothValSet, bothValSet, bothSigners), + Header1: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+3), height, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Hour), bothValSet, bothValSet, bothSigners), ClientId: chainID, }, suite.now, @@ -104,8 +109,8 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), heightMinus1, &types.Misbehaviour{ - Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ClientId: chainID, }, suite.now, @@ -119,8 +124,8 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), heightMinus3, &types.Misbehaviour{ - Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus3, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), + Header1: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus3, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), ClientId: chainID, }, suite.now, @@ -134,8 +139,8 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), heightMinus3, &types.Misbehaviour{ - Header1: chainADymint.CreateDMClientHeader(chainIDRevision0, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: chainADymint.CreateDMClientHeader(chainIDRevision0, int64(height.RevisionHeight+1), heightMinus3, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), + Header1: chainDymint.CreateDMClientHeader(chainIDRevision0, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainDymint.CreateDMClientHeader(chainIDRevision0, int64(height.RevisionHeight+1), heightMinus3, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), ClientId: chainID, }, suite.now, @@ -149,8 +154,8 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), heightMinus3, &types.Misbehaviour{ - Header1: chainADymint.CreateDMClientHeader(chainIDRevision0, 3, heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: chainADymint.CreateDMClientHeader(chainIDRevision0, 3, heightMinus3, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), + Header1: chainDymint.CreateDMClientHeader(chainIDRevision0, 3, heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainDymint.CreateDMClientHeader(chainIDRevision0, 3, heightMinus3, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), ClientId: chainID, }, suite.now, @@ -164,8 +169,8 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), heightMinus3, &types.Misbehaviour{ - Header1: chainADymint.CreateDMClientHeader(chainIDRevision1, 1, heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: chainADymint.CreateDMClientHeader(chainIDRevision1, 1, heightMinus3, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), + Header1: chainDymint.CreateDMClientHeader(chainIDRevision1, 1, heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainDymint.CreateDMClientHeader(chainIDRevision1, 1, heightMinus3, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), ClientId: chainID, }, suite.now, @@ -179,8 +184,8 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), height, &types.Misbehaviour{ - Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, suite.valSet, bothSigners), - Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), + Header1: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, suite.valSet, bothSigners), + Header2: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), ClientId: chainID, }, suite.now, @@ -194,8 +199,8 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), - Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), + Header1: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), ClientId: chainID, }, suite.now, @@ -209,8 +214,8 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+3), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), - Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), + Header1: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+3), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header2: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), ClientId: chainID, }, suite.now, @@ -224,8 +229,8 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: chainADymint.CreateDMClientHeader("ethermint", int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), - Header2: chainADymint.CreateDMClientHeader("ethermint", int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: chainDymint.CreateDMClientHeader("ethermint", int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainDymint.CreateDMClientHeader("ethermint", int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ClientId: chainID, }, suite.now, @@ -239,8 +244,8 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), heightMinus3, &types.Misbehaviour{ - Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), + Header1: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), ClientId: chainID, }, suite.now, @@ -254,8 +259,8 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), heightMinus3, &types.Misbehaviour{ - Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus3, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus3, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ClientId: chainID, }, suite.now, @@ -269,8 +274,8 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), - Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ClientId: chainID, }, suite.now, @@ -284,8 +289,8 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ClientId: chainID, }, suite.now, @@ -310,8 +315,8 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ClientId: chainID, }, suite.now, @@ -325,8 +330,8 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ClientId: chainID, }, suite.now.Add(trustingPeriod), @@ -340,8 +345,8 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, suite.valSet, bothSigners), - Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), + Header1: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, suite.valSet, bothSigners), + Header2: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), ClientId: chainID, }, suite.now, @@ -355,8 +360,8 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, altValSet, bothValSet, altSigners), - Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), + Header1: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, altValSet, bothValSet, altSigners), + Header2: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), ClientId: chainID, }, suite.now, @@ -370,8 +375,8 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), - Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), altValSet, bothValSet, altSigners), + Header1: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), + Header2: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), altValSet, bothValSet, altSigners), ClientId: chainID, }, suite.now, @@ -385,8 +390,8 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, &types.Misbehaviour{ - Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, altValSet, bothValSet, altSigners), - Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), altValSet, bothValSet, altSigners), + Header1: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, altValSet, bothValSet, altSigners), + Header2: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), altValSet, bothValSet, altSigners), ClientId: chainID, }, suite.now, @@ -400,22 +405,28 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { // reset suite to create fresh application state suite.SetupTest() + if suite.chainA.TestChainClient.GetSelfClientType() == exported.Dymint { + chainDymint = suite.chainA.TestChainClient.(*ibctesting.TestChainDymint) + } else { + chainDymint = suite.chainB.TestChainClient.(*ibctesting.TestChainDymint) + } + // Set current timestamp in context - ctx := suite.chainA.GetContext().WithBlockTime(tc.timestamp) + ctx := chainDymint.TC.GetContext().WithBlockTime(tc.timestamp) // Set trusted consensus states in client store if tc.consensusState1 != nil { - suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(ctx, clientID, tc.height1, tc.consensusState1) + chainDymint.TC.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(ctx, clientID, tc.height1, tc.consensusState1) } if tc.consensusState2 != nil { - suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(ctx, clientID, tc.height2, tc.consensusState2) + chainDymint.TC.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(ctx, clientID, tc.height2, tc.consensusState2) } clientState, err := tc.clientState.CheckMisbehaviourAndUpdateState( ctx, - suite.chainA.App.AppCodec(), - suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, clientID), // pass in clientID prefixed clientStore + chainDymint.TC.App.AppCodec(), + chainDymint.TC.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, clientID), // pass in clientID prefixed clientStore tc.misbehaviour, ) diff --git a/modules/light-clients/01-dymint/types/misbehaviour_test.go b/modules/light-clients/01-dymint/types/misbehaviour_test.go index 9c57fd905ff..6257e66f47d 100644 --- a/modules/light-clients/01-dymint/types/misbehaviour_test.go +++ b/modules/light-clients/01-dymint/types/misbehaviour_test.go @@ -15,14 +15,19 @@ import ( ) func (suite *DymintTestSuite) TestMisbehaviour() { + var dymintChain *ibctesting.TestChainDymint signers := []tmtypes.PrivValidator{suite.privVal} heightMinus1 := clienttypes.NewHeight(0, height.RevisionHeight-1) - chainADymint := suite.chainA.TestChainClient.(*ibctesting.TestChainDymint) + if suite.chainA.TestChainClient.GetSelfClientType() == exported.Dymint { + dymintChain = suite.chainA.TestChainClient.(*ibctesting.TestChainDymint) + } else { + dymintChain = suite.chainB.TestChainClient.(*ibctesting.TestChainDymint) + } misbehaviour := &types.Misbehaviour{ Header1: suite.header, - Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, signers), + Header2: dymintChain.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, signers), ClientId: clientID, } @@ -31,6 +36,7 @@ func (suite *DymintTestSuite) TestMisbehaviour() { } func (suite *DymintTestSuite) TestMisbehaviourValidateBasic() { + var dymintChain *ibctesting.TestChainDymint altPrivVal := ibctestingmock.NewPV() altPubKey, err := altPrivVal.GetPubKey() suite.Require().NoError(err) @@ -54,7 +60,11 @@ func (suite *DymintTestSuite) TestMisbehaviourValidateBasic() { heightMinus1 := clienttypes.NewHeight(0, height.RevisionHeight-1) - chainADymint := suite.chainA.TestChainClient.(*ibctesting.TestChainDymint) + if suite.chainA.TestChainClient.GetSelfClientType() == exported.Dymint { + dymintChain = suite.chainA.TestChainClient.(*ibctesting.TestChainDymint) + } else { + dymintChain = suite.chainB.TestChainClient.(*ibctesting.TestChainDymint) + } testCases := []struct { name string @@ -66,7 +76,7 @@ func (suite *DymintTestSuite) TestMisbehaviourValidateBasic() { "valid fork misbehaviour, two headers at same height have different time", &types.Misbehaviour{ Header1: suite.header, - Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers), + Header2: dymintChain.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers), ClientId: clientID, }, func(misbehaviour *types.Misbehaviour) error { return nil }, @@ -75,7 +85,7 @@ func (suite *DymintTestSuite) TestMisbehaviourValidateBasic() { { "valid time misbehaviour, both headers at different heights are at same time", &types.Misbehaviour{ - Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+5), heightMinus1, suite.now, suite.valSet, suite.valSet, signers), + Header1: dymintChain.CreateDMClientHeader(chainID, int64(height.RevisionHeight+5), heightMinus1, suite.now, suite.valSet, suite.valSet, signers), Header2: suite.header, ClientId: clientID, }, @@ -98,7 +108,7 @@ func (suite *DymintTestSuite) TestMisbehaviourValidateBasic() { "valid misbehaviour with different trusted headers", &types.Misbehaviour{ Header1: suite.header, - Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), clienttypes.NewHeight(0, height.RevisionHeight-3), suite.now.Add(time.Minute), suite.valSet, bothValSet, signers), + Header2: dymintChain.CreateDMClientHeader(chainID, int64(height.RevisionHeight), clienttypes.NewHeight(0, height.RevisionHeight-3), suite.now.Add(time.Minute), suite.valSet, bothValSet, signers), ClientId: clientID, }, func(misbehaviour *types.Misbehaviour) error { return nil }, @@ -107,7 +117,7 @@ func (suite *DymintTestSuite) TestMisbehaviourValidateBasic() { { "trusted height is 0 in Header1", &types.Misbehaviour{ - Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), clienttypes.ZeroHeight(), suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers), + Header1: dymintChain.CreateDMClientHeader(chainID, int64(height.RevisionHeight), clienttypes.ZeroHeight(), suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers), Header2: suite.header, ClientId: clientID, }, @@ -118,7 +128,7 @@ func (suite *DymintTestSuite) TestMisbehaviourValidateBasic() { "trusted height is 0 in Header2", &types.Misbehaviour{ Header1: suite.header, - Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), clienttypes.ZeroHeight(), suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers), + Header2: dymintChain.CreateDMClientHeader(chainID, int64(height.RevisionHeight), clienttypes.ZeroHeight(), suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers), ClientId: clientID, }, func(misbehaviour *types.Misbehaviour) error { return nil }, @@ -127,7 +137,7 @@ func (suite *DymintTestSuite) TestMisbehaviourValidateBasic() { { "trusted valset is nil in Header1", &types.Misbehaviour{ - Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now.Add(time.Minute), suite.valSet, nil, signers), + Header1: dymintChain.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now.Add(time.Minute), suite.valSet, nil, signers), Header2: suite.header, ClientId: clientID, }, @@ -138,7 +148,7 @@ func (suite *DymintTestSuite) TestMisbehaviourValidateBasic() { "trusted valset is nil in Header2", &types.Misbehaviour{ Header1: suite.header, - Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now.Add(time.Minute), suite.valSet, nil, signers), + Header2: dymintChain.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now.Add(time.Minute), suite.valSet, nil, signers), ClientId: clientID, }, func(misbehaviour *types.Misbehaviour) error { return nil }, @@ -148,7 +158,7 @@ func (suite *DymintTestSuite) TestMisbehaviourValidateBasic() { "invalid client ID ", &types.Misbehaviour{ Header1: suite.header, - Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, signers), + Header2: dymintChain.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, signers), ClientId: "GAIA", }, func(misbehaviour *types.Misbehaviour) error { return nil }, @@ -158,7 +168,7 @@ func (suite *DymintTestSuite) TestMisbehaviourValidateBasic() { "chainIDs do not match", &types.Misbehaviour{ Header1: suite.header, - Header2: chainADymint.CreateDMClientHeader("ethermint", int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, signers), + Header2: dymintChain.CreateDMClientHeader("ethermint", int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, signers), ClientId: clientID, }, func(misbehaviour *types.Misbehaviour) error { return nil }, @@ -168,7 +178,7 @@ func (suite *DymintTestSuite) TestMisbehaviourValidateBasic() { "header2 height is greater", &types.Misbehaviour{ Header1: suite.header, - Header2: chainADymint.CreateDMClientHeader(chainID, 6, clienttypes.NewHeight(0, height.RevisionHeight+1), suite.now, suite.valSet, suite.valSet, signers), + Header2: dymintChain.CreateDMClientHeader(chainID, 6, clienttypes.NewHeight(0, height.RevisionHeight+1), suite.now, suite.valSet, suite.valSet, signers), ClientId: clientID, }, func(misbehaviour *types.Misbehaviour) error { return nil }, @@ -177,7 +187,7 @@ func (suite *DymintTestSuite) TestMisbehaviourValidateBasic() { { "header 1 doesn't have 2/3 majority", &types.Misbehaviour{ - Header1: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, bothValSet, suite.valSet, bothSigners), + Header1: dymintChain.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, bothValSet, suite.valSet, bothSigners), Header2: suite.header, ClientId: clientID, }, @@ -199,7 +209,7 @@ func (suite *DymintTestSuite) TestMisbehaviourValidateBasic() { "header 2 doesn't have 2/3 majority", &types.Misbehaviour{ Header1: suite.header, - Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, bothValSet, suite.valSet, bothSigners), + Header2: dymintChain.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, bothValSet, suite.valSet, bothSigners), ClientId: clientID, }, func(misbehaviour *types.Misbehaviour) error { @@ -220,7 +230,7 @@ func (suite *DymintTestSuite) TestMisbehaviourValidateBasic() { "validators sign off on wrong commit", &types.Misbehaviour{ Header1: suite.header, - Header2: chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, bothValSet, suite.valSet, bothSigners), + Header2: dymintChain.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, bothValSet, suite.valSet, bothSigners), ClientId: clientID, }, func(misbehaviour *types.Misbehaviour) error { diff --git a/modules/light-clients/01-dymint/types/proposal_handle_test.go b/modules/light-clients/01-dymint/types/proposal_handle_test.go index 7443b42d9a8..e295e778868 100644 --- a/modules/light-clients/01-dymint/types/proposal_handle_test.go +++ b/modules/light-clients/01-dymint/types/proposal_handle_test.go @@ -1,11 +1,13 @@ package types_test import ( + fmt "fmt" "time" clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" "github.com/cosmos/ibc-go/v3/modules/core/exported" "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" + tmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" ibctesting "github.com/cosmos/ibc-go/v3/testing" ) @@ -30,11 +32,21 @@ func (suite *DymintTestSuite) TestCheckSubstituteUpdateStateBasic() { { "non-matching substitute", func() { suite.coordinator.SetupClients(substitutePath) - substituteClientState = suite.chainA.GetClientState(substitutePath.EndpointA.ClientID).(*types.ClientState) - tmClientState, ok := substituteClientState.(*types.ClientState) - suite.Require().True(ok) - - tmClientState.ChainId = tmClientState.ChainId + "different chain" + substituteClientState = suite.chainA.GetClientState(substitutePath.EndpointA.ClientID) + switch substituteClientState.ClientType() { + case exported.Dymint: + dmClientState, ok := substituteClientState.(*types.ClientState) + suite.Require().True(ok) + + dmClientState.ChainId = dmClientState.ChainId + "different chain" + case exported.Tendermint: + tmClientState, ok := substituteClientState.(*tmtypes.ClientState) + suite.Require().True(ok) + + tmClientState.ChainId = tmClientState.ChainId + "different chain" + default: + panic(fmt.Sprintf("client type %s is not supported", substituteClientState.ClientType())) + } }, }, } @@ -49,12 +61,24 @@ func (suite *DymintTestSuite) TestCheckSubstituteUpdateStateBasic() { substitutePath = ibctesting.NewPath(suite.chainA, suite.chainB) suite.coordinator.SetupClients(subjectPath) - subjectClientState := suite.chainA.GetClientState(subjectPath.EndpointA.ClientID).(*types.ClientState) - subjectClientState.AllowUpdateAfterMisbehaviour = true - subjectClientState.AllowUpdateAfterExpiry = true + subjectClientState := suite.chainA.GetClientState(subjectPath.EndpointA.ClientID) + switch subjectClientState.ClientType() { + case exported.Dymint: + subjectDMClientState := subjectClientState.(*types.ClientState) + subjectDMClientState.AllowUpdateAfterMisbehaviour = true + subjectDMClientState.AllowUpdateAfterExpiry = true + // expire subject client + suite.coordinator.IncrementTimeBy(subjectDMClientState.TrustingPeriod) + case exported.Tendermint: + subjectTMClientState := subjectClientState.(*tmtypes.ClientState) + subjectTMClientState.AllowUpdateAfterMisbehaviour = true + subjectTMClientState.AllowUpdateAfterExpiry = true + // expire subject client + suite.coordinator.IncrementTimeBy(subjectTMClientState.TrustingPeriod) + default: + panic(fmt.Sprintf("client type %s is not supported", subjectClientState.ClientType())) + } - // expire subject client - suite.coordinator.IncrementTimeBy(subjectClientState.TrustingPeriod) suite.coordinator.CommitBlock(suite.chainA, suite.chainB) tc.malleate() @@ -224,18 +248,40 @@ func (suite *DymintTestSuite) TestCheckSubstituteAndUpdateState() { // construct subject using test case parameters subjectPath := ibctesting.NewPath(suite.chainA, suite.chainB) suite.coordinator.SetupClients(subjectPath) - subjectClientState := suite.chainA.GetClientState(subjectPath.EndpointA.ClientID).(*types.ClientState) - subjectClientState.AllowUpdateAfterExpiry = tc.AllowUpdateAfterExpiry - subjectClientState.AllowUpdateAfterMisbehaviour = tc.AllowUpdateAfterMisbehaviour - // apply freezing or expiry as determined by the test case - if tc.FreezeClient { - subjectClientState.FrozenHeight = frozenHeight - } - if tc.ExpireClient { - // expire subject client - suite.coordinator.IncrementTimeBy(subjectClientState.TrustingPeriod) - suite.coordinator.CommitBlock(suite.chainA, suite.chainB) + subjectClientState := suite.chainA.GetClientState(subjectPath.EndpointA.ClientID) + switch subjectClientState.ClientType() { + case exported.Dymint: + subjectDMClientState := subjectClientState.(*types.ClientState) + subjectDMClientState.AllowUpdateAfterMisbehaviour = tc.AllowUpdateAfterMisbehaviour + subjectDMClientState.AllowUpdateAfterExpiry = tc.AllowUpdateAfterExpiry + + // apply freezing or expiry as determined by the test case + if tc.FreezeClient { + subjectDMClientState.FrozenHeight = frozenHeight + } + if tc.ExpireClient { + // expire subject client + suite.coordinator.IncrementTimeBy(subjectDMClientState.TrustingPeriod) + suite.coordinator.CommitBlock(suite.chainA, suite.chainB) + } + case exported.Tendermint: + subjectTMClientState := subjectClientState.(*tmtypes.ClientState) + subjectTMClientState.AllowUpdateAfterMisbehaviour = tc.AllowUpdateAfterMisbehaviour + subjectTMClientState.AllowUpdateAfterExpiry = tc.AllowUpdateAfterExpiry + + // apply freezing or expiry as determined by the test case + if tc.FreezeClient { + subjectTMClientState.FrozenHeight = frozenHeight + } + if tc.ExpireClient { + // expire subject client + suite.coordinator.IncrementTimeBy(subjectTMClientState.TrustingPeriod) + suite.coordinator.CommitBlock(suite.chainA, suite.chainB) + } + + default: + panic(fmt.Sprintf("client type %s is not supported", subjectClientState.ClientType())) } // construct the substitute to match the subject client @@ -246,9 +292,21 @@ func (suite *DymintTestSuite) TestCheckSubstituteAndUpdateState() { substitutePath := ibctesting.NewPath(suite.chainA, suite.chainB) suite.coordinator.SetupClients(substitutePath) - substituteClientState := suite.chainA.GetClientState(substitutePath.EndpointA.ClientID).(*types.ClientState) - substituteClientState.AllowUpdateAfterExpiry = tc.AllowUpdateAfterExpiry - substituteClientState.AllowUpdateAfterMisbehaviour = tc.AllowUpdateAfterMisbehaviour + + substituteClientState := suite.chainA.GetClientState(substitutePath.EndpointA.ClientID) + switch substituteClientState.ClientType() { + case exported.Dymint: + substituteDMClientState := substituteClientState.(*types.ClientState) + substituteDMClientState.AllowUpdateAfterMisbehaviour = tc.AllowUpdateAfterMisbehaviour + substituteDMClientState.AllowUpdateAfterExpiry = tc.AllowUpdateAfterExpiry + case exported.Tendermint: + substituteTMClientState := substituteClientState.(*tmtypes.ClientState) + substituteTMClientState.AllowUpdateAfterMisbehaviour = tc.AllowUpdateAfterMisbehaviour + substituteTMClientState.AllowUpdateAfterExpiry = tc.AllowUpdateAfterExpiry + default: + panic(fmt.Sprintf("client type %s is not supported", subjectClientState.ClientType())) + } + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), substitutePath.EndpointA.ClientID, substituteClientState) // update substitute a few times @@ -259,12 +317,20 @@ func (suite *DymintTestSuite) TestCheckSubstituteAndUpdateState() { suite.coordinator.CommitBlock(suite.chainA, suite.chainB) } - // get updated substitute - substituteClientState = suite.chainA.GetClientState(substitutePath.EndpointA.ClientID).(*types.ClientState) - // test that subject gets updated chain-id newChainID := "new-chain-id" - substituteClientState.ChainId = newChainID + // get updated substitute + substituteClientState = suite.chainA.GetClientState(substitutePath.EndpointA.ClientID) + switch substituteClientState.ClientType() { + case exported.Dymint: + substituteDMClientState := substituteClientState.(*types.ClientState) + substituteDMClientState.ChainId = newChainID + case exported.Tendermint: + substituteTMClientState := substituteClientState.(*tmtypes.ClientState) + substituteTMClientState.ChainId = newChainID + default: + panic(fmt.Sprintf("client type %s is not supported", subjectClientState.ClientType())) + } 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) @@ -280,7 +346,21 @@ func (suite *DymintTestSuite) TestCheckSubstituteAndUpdateState() { if tc.expPass { suite.Require().NoError(err) - suite.Require().Equal(clienttypes.ZeroHeight(), updatedClient.(*types.ClientState).FrozenHeight) + + updatedClientChainId := newChainID + FrozenHeight := clienttypes.ZeroHeight() + switch updatedClient.ClientType() { + case exported.Dymint: + updatedClientChainId = updatedClient.(*types.ClientState).ChainId + FrozenHeight = updatedClient.(*types.ClientState).FrozenHeight + case exported.Tendermint: + updatedClientChainId = updatedClient.(*tmtypes.ClientState).ChainId + FrozenHeight = updatedClient.(*tmtypes.ClientState).FrozenHeight + default: + panic(fmt.Sprintf("client type %s is not supported", subjectClientState.ClientType())) + } + suite.Require().Equal(newChainID, updatedClientChainId) + suite.Require().Equal(clienttypes.ZeroHeight(), FrozenHeight) subjectClientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), subjectPath.EndpointA.ClientID) @@ -298,7 +378,6 @@ func (suite *DymintTestSuite) TestCheckSubstituteAndUpdateState() { suite.Require().Equal(expectedProcessedHeight, subjectProcessedHeight) suite.Require().Equal(expectedIterationKey, subjectIterationKey) - suite.Require().Equal(newChainID, updatedClient.(*types.ClientState).ChainId) } else { suite.Require().Error(err) suite.Require().Nil(updatedClient) @@ -321,8 +400,18 @@ func (suite *DymintTestSuite) TestIsMatchingClientState() { }{ { "matching clients", func() { - subjectClientState = suite.chainA.GetClientState(subjectPath.EndpointA.ClientID).(*types.ClientState) - substituteClientState = suite.chainA.GetClientState(substitutePath.EndpointA.ClientID).(*types.ClientState) + switch suite.chainA.TestChainClient.GetSelfClientType() { + case exported.Dymint: + // ChainBs' counterparty client is Dymint + subjectClientState = suite.chainB.GetClientState(subjectPath.EndpointB.ClientID).(*types.ClientState) + substituteClientState = suite.chainB.GetClientState(substitutePath.EndpointB.ClientID).(*types.ClientState) + case exported.Tendermint: + // ChainAs' counterparty client is Dymint + subjectClientState = suite.chainA.GetClientState(subjectPath.EndpointA.ClientID).(*types.ClientState) + substituteClientState = suite.chainA.GetClientState(substitutePath.EndpointA.ClientID).(*types.ClientState) + default: + panic(fmt.Sprintf("client type %s is not supported", subjectClientState.ClientType())) + } }, true, }, { diff --git a/modules/light-clients/01-dymint/types/store_test.go b/modules/light-clients/01-dymint/types/store_test.go index a28c6bb3790..86500130482 100644 --- a/modules/light-clients/01-dymint/types/store_test.go +++ b/modules/light-clients/01-dymint/types/store_test.go @@ -15,8 +15,10 @@ import ( func (suite *DymintTestSuite) TestGetConsensusState() { var ( - height exported.Height - path *ibctesting.Path + height exported.Height + path *ibctesting.Path + dymintCounterpartyChain *ibctesting.TestChain + endpointClientID string ) testCases := []struct { @@ -36,16 +38,16 @@ func (suite *DymintTestSuite) TestGetConsensusState() { { "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 := suite.chainA.App.GetIBCKeeper().ClientKeeper.MustMarshalClientState(&types.ClientState{}) + store := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.ClientStore(dymintCounterpartyChain.GetContext(), endpointClientID) + clientStateBz := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.MustMarshalClientState(&types.ClientState{}) store.Set(host.ConsensusStateKey(height), clientStateBz) }, false, }, { "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 := suite.chainA.App.GetIBCKeeper().ClientKeeper.MustMarshalConsensusState(&solomachinetypes.ConsensusState{}) + store := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.ClientStore(dymintCounterpartyChain.GetContext(), endpointClientID) + consensusStateBz := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.MustMarshalConsensusState(&solomachinetypes.ConsensusState{}) store.Set(host.ConsensusStateKey(height), consensusStateBz) }, false, }, @@ -59,17 +61,27 @@ func (suite *DymintTestSuite) TestGetConsensusState() { path = ibctesting.NewPath(suite.chainA, suite.chainB) suite.coordinator.Setup(path) - clientState := suite.chainA.GetClientState(path.EndpointA.ClientID) + + if suite.chainA.TestChainClient.GetSelfClientType() == exported.Dymint { + dymintCounterpartyChain = suite.chainB + endpointClientID = path.EndpointB.ClientID + } else { + // chainB must be Dymint + dymintCounterpartyChain = suite.chainA + endpointClientID = path.EndpointA.ClientID + } + + clientState := dymintCounterpartyChain.GetClientState(endpointClientID) height = clientState.GetLatestHeight() tc.malleate() // change vars as necessary - store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) - consensusState, err := types.GetConsensusState(store, suite.chainA.Codec, height) + store := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.ClientStore(dymintCounterpartyChain.GetContext(), endpointClientID) + consensusState, err := types.GetConsensusState(store, dymintCounterpartyChain.Codec, height) if tc.expPass { suite.Require().NoError(err) - expConsensusState, found := suite.chainA.GetConsensusState(path.EndpointA.ClientID, height) + expConsensusState, found := dymintCounterpartyChain.GetConsensusState(endpointClientID, height) suite.Require().True(found) suite.Require().Equal(expConsensusState, consensusState) } else { @@ -81,37 +93,61 @@ func (suite *DymintTestSuite) TestGetConsensusState() { } func (suite *DymintTestSuite) TestGetProcessedTime() { + var ( + dymintCounterpartyChain *ibctesting.TestChain + endpoint *ibctesting.Endpoint + expectedTime time.Time + ) // setup path := ibctesting.NewPath(suite.chainA, suite.chainB) suite.coordinator.UpdateTime() - // coordinator increments time before creating client - expectedTime := suite.chainA.TestChainClient.(*ibctesting.TestChainDymint).CurrentHeader.Time.Add(ibctesting.TimeIncrement) + + if suite.chainB.TestChainClient.GetSelfClientType() == exported.Tendermint { + // chainA must be Dymint + dymintCounterpartyChain = suite.chainB + endpoint = path.EndpointB + // coordinator increments time before creating client + expectedTime = dymintCounterpartyChain.TestChainClient.(*ibctesting.TestChainTendermint).CurrentHeader.Time.Add(ibctesting.TimeIncrement) + } else { + // chainB must be Dymint + dymintCounterpartyChain = suite.chainA + endpoint = path.EndpointA + if dymintCounterpartyChain.TestChainClient.GetSelfClientType() == exported.Tendermint { + expectedTime = dymintCounterpartyChain.TestChainClient.(*ibctesting.TestChainTendermint).CurrentHeader.Time.Add(ibctesting.TimeIncrement) + } else { + expectedTime = dymintCounterpartyChain.TestChainClient.(*ibctesting.TestChainDymint).CurrentHeader.Time.Add(ibctesting.TimeIncrement) + } + } // Verify ProcessedTime on CreateClient - err := path.EndpointA.CreateClient() + err := endpoint.CreateClient() suite.Require().NoError(err) - clientState := suite.chainA.GetClientState(path.EndpointA.ClientID) + clientState := dymintCounterpartyChain.GetClientState(endpoint.ClientID) height := clientState.GetLatestHeight() - store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + store := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.ClientStore(dymintCounterpartyChain.GetContext(), endpoint.ClientID) actualTime, ok := types.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.TestChainClient.(*ibctesting.TestChainDymint).CurrentHeader.Time.Add(ibctesting.TimeIncrement) + if dymintCounterpartyChain.TestChainClient.GetSelfClientType() == exported.Tendermint { + expectedTime = dymintCounterpartyChain.TestChainClient.(*ibctesting.TestChainTendermint).CurrentHeader.Time.Add(ibctesting.TimeIncrement) + } else { + expectedTime = dymintCounterpartyChain.TestChainClient.(*ibctesting.TestChainDymint).CurrentHeader.Time.Add(ibctesting.TimeIncrement) + } // Verify ProcessedTime on UpdateClient - err = path.EndpointA.UpdateClient() + err = endpoint.UpdateClient() suite.Require().NoError(err) - clientState = suite.chainA.GetClientState(path.EndpointA.ClientID) + clientState = dymintCounterpartyChain.GetClientState(endpoint.ClientID) height = clientState.GetLatestHeight() - store = suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + store = dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.ClientStore(dymintCounterpartyChain.GetContext(), endpoint.ClientID) actualTime, ok = types.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") @@ -136,19 +172,28 @@ func (suite *DymintTestSuite) TestIterationKey() { } func (suite *DymintTestSuite) TestIterateConsensusStates() { + var dymintCounterpartyChain *ibctesting.TestChain + if suite.chainB.TestChainClient.GetSelfClientType() == exported.Tendermint { + // chainA must be Dymint + dymintCounterpartyChain = suite.chainB + } else { + // chainB must be Dymint + dymintCounterpartyChain = suite.chainA + } + nextValsHash := []byte("nextVals") // Set iteration keys and consensus states - types.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), types.NewConsensusState(time.Now(), commitmenttypes.NewMerkleRoot([]byte("hash0-1")), nextValsHash)) - types.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), types.NewConsensusState(time.Now(), commitmenttypes.NewMerkleRoot([]byte("hash4-9")), nextValsHash)) - types.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), types.NewConsensusState(time.Now(), commitmenttypes.NewMerkleRoot([]byte("hash0-10")), nextValsHash)) - types.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), types.NewConsensusState(time.Now(), commitmenttypes.NewMerkleRoot([]byte("hash0-4")), nextValsHash)) - types.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), types.NewConsensusState(time.Now(), commitmenttypes.NewMerkleRoot([]byte("hash40-1")), nextValsHash)) + types.SetIterationKey(dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.ClientStore(dymintCounterpartyChain.GetContext(), "testClient"), clienttypes.NewHeight(0, 1)) + dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(dymintCounterpartyChain.GetContext(), "testClient", clienttypes.NewHeight(0, 1), types.NewConsensusState(time.Now(), commitmenttypes.NewMerkleRoot([]byte("hash0-1")), nextValsHash)) + types.SetIterationKey(dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.ClientStore(dymintCounterpartyChain.GetContext(), "testClient"), clienttypes.NewHeight(4, 9)) + dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(dymintCounterpartyChain.GetContext(), "testClient", clienttypes.NewHeight(4, 9), types.NewConsensusState(time.Now(), commitmenttypes.NewMerkleRoot([]byte("hash4-9")), nextValsHash)) + types.SetIterationKey(dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.ClientStore(dymintCounterpartyChain.GetContext(), "testClient"), clienttypes.NewHeight(0, 10)) + dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(dymintCounterpartyChain.GetContext(), "testClient", clienttypes.NewHeight(0, 10), types.NewConsensusState(time.Now(), commitmenttypes.NewMerkleRoot([]byte("hash0-10")), nextValsHash)) + types.SetIterationKey(dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.ClientStore(dymintCounterpartyChain.GetContext(), "testClient"), clienttypes.NewHeight(0, 4)) + dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(dymintCounterpartyChain.GetContext(), "testClient", clienttypes.NewHeight(0, 4), types.NewConsensusState(time.Now(), commitmenttypes.NewMerkleRoot([]byte("hash0-4")), nextValsHash)) + types.SetIterationKey(dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.ClientStore(dymintCounterpartyChain.GetContext(), "testClient"), clienttypes.NewHeight(40, 1)) + dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(dymintCounterpartyChain.GetContext(), "testClient", clienttypes.NewHeight(40, 1), types.NewConsensusState(time.Now(), commitmenttypes.NewMerkleRoot([]byte("hash40-1")), nextValsHash)) var testArr []string cb := func(height exported.Height) bool { @@ -156,12 +201,21 @@ func (suite *DymintTestSuite) TestIterateConsensusStates() { return false } - types.IterateConsensusStateAscending(suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), "testClient"), cb) + types.IterateConsensusStateAscending(dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.ClientStore(dymintCounterpartyChain.GetContext(), "testClient"), cb) expectedArr := []string{"0-1", "0-4", "0-10", "4-9", "40-1"} suite.Require().Equal(expectedArr, testArr) } func (suite *DymintTestSuite) TestGetNeighboringConsensusStates() { + var dymintCounterpartyChain *ibctesting.TestChain + if suite.chainB.TestChainClient.GetSelfClientType() == exported.Tendermint { + // chainA must be Dymint + dymintCounterpartyChain = suite.chainB + } else { + // chainB must be Dymint + dymintCounterpartyChain = suite.chainA + } + nextValsHash := []byte("nextVals") cs01 := types.NewConsensusState(time.Now().UTC(), commitmenttypes.NewMerkleRoot([]byte("hash0-1")), nextValsHash) cs04 := types.NewConsensusState(time.Now().UTC(), commitmenttypes.NewMerkleRoot([]byte("hash0-4")), nextValsHash) @@ -171,24 +225,24 @@ func (suite *DymintTestSuite) TestGetNeighboringConsensusStates() { height49 := clienttypes.NewHeight(4, 9) // Set iteration keys and consensus states - types.SetIterationKey(suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), "testClient"), height01) - suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), "testClient", height01, cs01) - types.SetIterationKey(suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), "testClient"), height04) - suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), "testClient", height04, cs04) - types.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 := types.GetPreviousConsensusState(suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), "testClient"), suite.chainA.Codec, height01) + types.SetIterationKey(dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.ClientStore(dymintCounterpartyChain.GetContext(), "testClient"), height01) + dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(dymintCounterpartyChain.GetContext(), "testClient", height01, cs01) + types.SetIterationKey(dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.ClientStore(dymintCounterpartyChain.GetContext(), "testClient"), height04) + dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(dymintCounterpartyChain.GetContext(), "testClient", height04, cs04) + types.SetIterationKey(dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.ClientStore(dymintCounterpartyChain.GetContext(), "testClient"), height49) + dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(dymintCounterpartyChain.GetContext(), "testClient", height49, cs49) + + prevCs01, ok := types.GetPreviousConsensusState(dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.ClientStore(dymintCounterpartyChain.GetContext(), "testClient"), dymintCounterpartyChain.Codec, height01) suite.Require().Nil(prevCs01, "consensus state exists before lowest consensus state") suite.Require().False(ok) - prevCs49, ok := types.GetPreviousConsensusState(suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), "testClient"), suite.chainA.Codec, height49) + prevCs49, ok := types.GetPreviousConsensusState(dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.ClientStore(dymintCounterpartyChain.GetContext(), "testClient"), dymintCounterpartyChain.Codec, height49) suite.Require().Equal(cs04, prevCs49, "previous consensus state is not returned correctly") suite.Require().True(ok) - nextCs01, ok := types.GetNextConsensusState(suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), "testClient"), suite.chainA.Codec, height01) + nextCs01, ok := types.GetNextConsensusState(dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.ClientStore(dymintCounterpartyChain.GetContext(), "testClient"), dymintCounterpartyChain.Codec, height01) suite.Require().Equal(cs04, nextCs01, "next consensus state not returned correctly") suite.Require().True(ok) - nextCs49, ok := types.GetNextConsensusState(suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), "testClient"), suite.chainA.Codec, height49) + nextCs49, ok := types.GetNextConsensusState(dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.ClientStore(dymintCounterpartyChain.GetContext(), "testClient"), dymintCounterpartyChain.Codec, height49) suite.Require().Nil(nextCs49, "next consensus state exists after highest consensus state") suite.Require().False(ok) } diff --git a/modules/light-clients/01-dymint/types/update_test.go b/modules/light-clients/01-dymint/types/update_test.go index 1c50fda14c3..42c66b42383 100644 --- a/modules/light-clients/01-dymint/types/update_test.go +++ b/modules/light-clients/01-dymint/types/update_test.go @@ -44,7 +44,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { altValSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{altVal}) altSigners := []tmtypes.PrivValidator{altPrivVal} - chainADymint := suite.chainA.TestChainClient.(*ibctesting.TestChainDymint) + var chainADymint *ibctesting.TestChainDymint testCases := []struct { name string @@ -314,7 +314,9 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { for i, tc := range testCases { tc := tc suite.Run(fmt.Sprintf("Case: %s", tc.name), func() { - suite.SetupTest() // reset metadata writes + suite.SetupTestWithConsensusType(exported.Dymint, exported.Tendermint) // reset + chainADymint = suite.chainA.TestChainClient.(*ibctesting.TestChainDymint) + // Create bothValSet with both suite validator and altVal. Would be valid update bothValSet = tmtypes.NewValidatorSet(append(suite.valSet.Validators, altVal)) signers = []tmtypes.PrivValidator{suite.privVal} diff --git a/modules/light-clients/01-dymint/types/upgrade_test.go b/modules/light-clients/01-dymint/types/upgrade_test.go index e8463550531..bea049a5c3d 100644 --- a/modules/light-clients/01-dymint/types/upgrade_test.go +++ b/modules/light-clients/01-dymint/types/upgrade_test.go @@ -23,6 +23,8 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { proofUpgradedClient, proofUpgradedConsState []byte upgradedClientBz, upgradedConsStateBz []byte err error + dymintCounterpartyChain, dymintChain *ibctesting.TestChain + endpoint *ibctesting.Endpoint ) testCases := []struct { @@ -34,54 +36,54 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { name: "successful upgrade", setup: func() { // upgrade Height is at next block - lastHeight = clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+1)) + lastHeight = clienttypes.NewHeight(0, uint64(dymintChain.GetContext().BlockHeight()+1)) // zero custom fields and store in upgrade store - suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) - suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) + dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedClient(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) + dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) // commit upgrade store changes and update clients - suite.coordinator.CommitBlock(suite.chainB) - err := path.EndpointA.UpdateClient() + suite.coordinator.CommitBlock(dymintChain) + err := endpoint.UpdateClient() suite.Require().NoError(err) - cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + cs, found := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.GetClientState(dymintCounterpartyChain.GetContext(), endpoint.ClientID) suite.Require().True(found) - proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) - proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedClient, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) }, expPass: true, }, { name: "successful upgrade to same revision", setup: func() { - upgradedHeight := clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+2)) + upgradedHeight := clienttypes.NewHeight(0, uint64(dymintChain.GetContext().BlockHeight()+2)) // don't use -1 suffix in chain id upgradedClient = types.NewClientState("newChainId", types.DefaultTrustLevel, trustingPeriod, ubdPeriod+trustingPeriod, maxClockDrift, upgradedHeight, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) upgradedClient = upgradedClient.ZeroCustomFields() - upgradedClientBz, err = clienttypes.MarshalClientState(suite.chainA.App.AppCodec(), upgradedClient) + upgradedClientBz, err = clienttypes.MarshalClientState(dymintCounterpartyChain.App.AppCodec(), upgradedClient) suite.Require().NoError(err) // upgrade Height is at next block - lastHeight = clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+1)) + lastHeight = clienttypes.NewHeight(0, uint64(dymintChain.GetContext().BlockHeight()+1)) // zero custom fields and store in upgrade store - suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) - suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) + dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedClient(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) + dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) // commit upgrade store changes and update clients - suite.coordinator.CommitBlock(suite.chainB) - err := path.EndpointA.UpdateClient() + suite.coordinator.CommitBlock(dymintChain) + err := endpoint.UpdateClient() suite.Require().NoError(err) - cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + cs, found := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.GetClientState(dymintCounterpartyChain.GetContext(), endpoint.ClientID) suite.Require().True(found) - proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) - proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedClient, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) }, expPass: true, }, @@ -90,23 +92,23 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { 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)) + lastHeight = clienttypes.NewHeight(0, uint64(dymintChain.GetContext().BlockHeight()+10)) // zero custom fields and store in upgrade store - suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) - suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) + dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedClient(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) + dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) // commit upgrade store changes and update clients - suite.coordinator.CommitBlock(suite.chainB) - err := path.EndpointA.UpdateClient() + suite.coordinator.CommitBlock(dymintChain) + err := endpoint.UpdateClient() suite.Require().NoError(err) - cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + cs, found := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.GetClientState(dymintCounterpartyChain.GetContext(), endpoint.ClientID) suite.Require().True(found) - proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) - proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedClient, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) }, expPass: false, }, @@ -115,27 +117,27 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { setup: func() { // non-zeroed upgrade client upgradedClient = types.NewClientState(newChainId, types.DefaultTrustLevel, trustingPeriod, ubdPeriod+trustingPeriod, maxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) - upgradedClientBz, err = clienttypes.MarshalClientState(suite.chainA.App.AppCodec(), upgradedClient) + upgradedClientBz, err = clienttypes.MarshalClientState(dymintCounterpartyChain.App.AppCodec(), upgradedClient) suite.Require().NoError(err) // upgrade Height is at next block - lastHeight = clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+1)) + lastHeight = clienttypes.NewHeight(0, uint64(dymintChain.GetContext().BlockHeight()+1)) // zero custom fields and store in upgrade store - suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) - suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) + dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedClient(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) + dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) // commit upgrade store changes and update clients - suite.coordinator.CommitBlock(suite.chainB) - err := path.EndpointA.UpdateClient() + suite.coordinator.CommitBlock(dymintChain) + err := endpoint.UpdateClient() suite.Require().NoError(err) - cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + cs, found := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.GetClientState(dymintCounterpartyChain.GetContext(), endpoint.ClientID) suite.Require().True(found) - proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) - proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedClient, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) }, expPass: false, }, @@ -143,24 +145,24 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { 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)) + lastHeight = clienttypes.NewHeight(0, uint64(dymintChain.GetContext().BlockHeight()+1)) // zero custom fields and store in upgrade store - suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) - suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) + dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedClient(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) + dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) // change upgradedClient client-specified parameters upgradedClient = types.NewClientState("wrongchainID", types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), upgradePath, true, true) - suite.coordinator.CommitBlock(suite.chainB) - err := path.EndpointA.UpdateClient() + suite.coordinator.CommitBlock(dymintChain) + err := endpoint.UpdateClient() suite.Require().NoError(err) - cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + cs, found := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.GetClientState(dymintCounterpartyChain.GetContext(), endpoint.ClientID) suite.Require().True(found) - proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) - proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedClient, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) }, expPass: false, }, @@ -168,21 +170,21 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { name: "unsuccessful upgrade: client-specified parameters do not match previous client", setup: func() { // zero custom fields and store in upgrade store - suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) - suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) + dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedClient(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) + dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) // change upgradedClient client-specified parameters upgradedClient = types.NewClientState(newChainId, types.DefaultTrustLevel, ubdPeriod, ubdPeriod+trustingPeriod, maxClockDrift+5, lastHeight, commitmenttypes.GetSDKSpecs(), upgradePath, true, false) - suite.coordinator.CommitBlock(suite.chainB) - err := path.EndpointA.UpdateClient() + suite.coordinator.CommitBlock(dymintChain) + err := endpoint.UpdateClient() suite.Require().NoError(err) - cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + cs, found := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.GetClientState(dymintCounterpartyChain.GetContext(), endpoint.ClientID) suite.Require().True(found) - proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) - proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedClient, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) }, expPass: false, }, @@ -190,11 +192,11 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { name: "unsuccessful upgrade: relayer-submitted consensus state does not match counterparty-committed consensus state", setup: func() { // upgrade Height is at next block - lastHeight = clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+1)) + lastHeight = clienttypes.NewHeight(0, uint64(dymintChain.GetContext().BlockHeight()+1)) // zero custom fields and store in upgrade store - suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) - suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) + dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedClient(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) + dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) // change submitted upgradedConsensusState upgradedConsState = &types.ConsensusState{ @@ -203,27 +205,27 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { // commit upgrade store changes and update clients - suite.coordinator.CommitBlock(suite.chainB) - err := path.EndpointA.UpdateClient() + suite.coordinator.CommitBlock(dymintChain) + err := endpoint.UpdateClient() suite.Require().NoError(err) - cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + cs, found := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.GetClientState(dymintCounterpartyChain.GetContext(), endpoint.ClientID) suite.Require().True(found) - proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) - proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedClient, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) }, expPass: false, }, { name: "unsuccessful upgrade: client proof unmarshal failed", setup: func() { - suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) + dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) - cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + cs, found := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.GetClientState(dymintCounterpartyChain.GetContext(), endpoint.ClientID) suite.Require().True(found) - proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) proofUpgradedClient = []byte("proof") }, @@ -232,12 +234,12 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { { name: "unsuccessful upgrade: consensus state proof unmarshal failed", setup: func() { - suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) + dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedClient(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) - cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + cs, found := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.GetClientState(dymintCounterpartyChain.GetContext(), endpoint.ClientID) suite.Require().True(found) - proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedClient, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) proofUpgradedConsState = []byte("proof") }, @@ -249,15 +251,15 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { // do not store upgraded client // upgrade Height is at next block - lastHeight = clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+1)) + lastHeight = clienttypes.NewHeight(0, uint64(dymintChain.GetContext().BlockHeight()+1)) - suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) + dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) - cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + cs, found := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.GetClientState(dymintCounterpartyChain.GetContext(), endpoint.ClientID) suite.Require().True(found) - proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) - proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedClient, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) }, expPass: false, }, @@ -267,15 +269,15 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { // do not store upgraded client // upgrade Height is at next block - lastHeight = clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+1)) + lastHeight = clienttypes.NewHeight(0, uint64(dymintChain.GetContext().BlockHeight()+1)) - suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) + dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedClient(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) - cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + cs, found := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.GetClientState(dymintCounterpartyChain.GetContext(), endpoint.ClientID) suite.Require().True(found) - proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) - proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedClient, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) }, expPass: false, }, @@ -283,27 +285,27 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { name: "unsuccessful upgrade: upgrade path is empty", setup: func() { // upgrade Height is at next block - lastHeight = clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+1)) + lastHeight = clienttypes.NewHeight(0, uint64(dymintChain.GetContext().BlockHeight()+1)) // zero custom fields and store in upgrade store - suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) + dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedClient(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) // commit upgrade store changes and update clients - suite.coordinator.CommitBlock(suite.chainB) - err := path.EndpointA.UpdateClient() + suite.coordinator.CommitBlock(dymintChain) + err := endpoint.UpdateClient() suite.Require().NoError(err) - cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + cs, found := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.GetClientState(dymintCounterpartyChain.GetContext(), endpoint.ClientID) suite.Require().True(found) - proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) - proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedClient, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) // SetClientState with empty upgrade path tmClient, _ := cs.(*types.ClientState) tmClient.UpgradePath = []string{""} - suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID, tmClient) + dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.SetClientState(dymintCounterpartyChain.GetContext(), endpoint.ClientID, tmClient) }, expPass: false, }, @@ -311,22 +313,22 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { 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)) + lastHeight = clienttypes.NewHeight(0, uint64(dymintChain.GetContext().BlockHeight()+1)) // zero custom fields and store in upgrade store - suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) + dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedClient(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) // commit upgrade store changes and update clients - suite.coordinator.CommitBlock(suite.chainB) - err := path.EndpointA.UpdateClient() + suite.coordinator.CommitBlock(dymintChain) + err := endpoint.UpdateClient() suite.Require().NoError(err) - cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + cs, found := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.GetClientState(dymintCounterpartyChain.GetContext(), endpoint.ClientID) suite.Require().True(found) - proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) - proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedClient, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) }, expPass: false, }, @@ -334,22 +336,22 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { 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)) + lastHeight = clienttypes.NewHeight(0, uint64(dymintChain.GetContext().BlockHeight()+100)) // zero custom fields and store in upgrade store - suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) + dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedClient(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) // commit upgrade store changes and update clients - suite.coordinator.CommitBlock(suite.chainB) - err := path.EndpointA.UpdateClient() + suite.coordinator.CommitBlock(dymintChain) + err := endpoint.UpdateClient() suite.Require().NoError(err) - cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + cs, found := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.GetClientState(dymintCounterpartyChain.GetContext(), endpoint.ClientID) suite.Require().True(found) - proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) - proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedClient, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) }, expPass: false, }, @@ -357,22 +359,22 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { 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) + dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedClient(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) // commit upgrade store changes and update clients - suite.coordinator.CommitBlock(suite.chainB) - err := path.EndpointA.UpdateClient() + suite.coordinator.CommitBlock(dymintChain) + err := endpoint.UpdateClient() suite.Require().NoError(err) // expire chainB's client - suite.chainA.ExpireClient(ubdPeriod) + dymintCounterpartyChain.ExpireClient(ubdPeriod) - cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + cs, found := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.GetClientState(dymintCounterpartyChain.GetContext(), endpoint.ClientID) suite.Require().True(found) - proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) - proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedClient, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) }, expPass: false, }, @@ -380,22 +382,22 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { 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)) + lastHeight = clienttypes.NewHeight(0, uint64(dymintChain.GetContext().BlockHeight()+1)) // zero custom fields and store in upgrade store - suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) + dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedClient(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) // commit upgrade store changes and update clients - suite.coordinator.CommitBlock(suite.chainB) - err := path.EndpointA.UpdateClient() + suite.coordinator.CommitBlock(dymintChain) + err := endpoint.UpdateClient() suite.Require().NoError(err) - cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + cs, found := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.GetClientState(dymintCounterpartyChain.GetContext(), endpoint.ClientID) suite.Require().True(found) - proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) - proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedClient, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) }, expPass: false, }, @@ -404,27 +406,27 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { setup: func() { // new client has smaller unbonding period such that old trusting period is no longer valid upgradedClient = types.NewClientState(newChainId, types.DefaultTrustLevel, trustingPeriod, trustingPeriod, maxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) - upgradedClientBz, err = clienttypes.MarshalClientState(suite.chainA.App.AppCodec(), upgradedClient) + upgradedClientBz, err = clienttypes.MarshalClientState(dymintCounterpartyChain.App.AppCodec(), upgradedClient) suite.Require().NoError(err) // upgrade Height is at next block - lastHeight = clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+1)) + lastHeight = clienttypes.NewHeight(0, uint64(dymintChain.GetContext().BlockHeight()+1)) // zero custom fields and store in upgrade store - suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) - suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) + dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedClient(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) + dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) // commit upgrade store changes and update clients - suite.coordinator.CommitBlock(suite.chainB) - err := path.EndpointA.UpdateClient() + suite.coordinator.CommitBlock(dymintChain) + err := endpoint.UpdateClient() suite.Require().NoError(err) - cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + cs, found := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.GetClientState(dymintCounterpartyChain.GetContext(), endpoint.ClientID) suite.Require().True(found) - proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) - proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedClient, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) + proofUpgradedConsState, _ = dymintChain.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight()) }, expPass: false, }, @@ -438,27 +440,38 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { path = ibctesting.NewPath(suite.chainA, suite.chainB) suite.coordinator.SetupClients(path) + + if suite.chainA.TestChainClient.GetSelfClientType() == exported.Dymint { + dymintChain = suite.chainA + dymintCounterpartyChain = suite.chainB + endpoint = path.EndpointB + } else { + dymintChain = suite.chainB + dymintCounterpartyChain = suite.chainA + endpoint = path.EndpointA + } + upgradedClient = types.NewClientState(newChainId, types.DefaultTrustLevel, trustingPeriod, ubdPeriod+trustingPeriod, maxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) upgradedClient = upgradedClient.ZeroCustomFields() - upgradedClientBz, err = clienttypes.MarshalClientState(suite.chainA.App.AppCodec(), upgradedClient) + upgradedClientBz, err = clienttypes.MarshalClientState(dymintCounterpartyChain.App.AppCodec(), upgradedClient) suite.Require().NoError(err) upgradedConsState = &types.ConsensusState{ NextValidatorsHash: []byte("nextValsHash"), } - upgradedConsStateBz, err = clienttypes.MarshalConsensusState(suite.chainA.App.AppCodec(), upgradedConsState) + upgradedConsStateBz, err = clienttypes.MarshalConsensusState(dymintCounterpartyChain.App.AppCodec(), upgradedConsState) suite.Require().NoError(err) tc.setup() - cs := suite.chainA.GetClientState(path.EndpointA.ClientID) - clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + cs := dymintCounterpartyChain.GetClientState(endpoint.ClientID) + clientStore := dymintCounterpartyChain.App.GetIBCKeeper().ClientKeeper.ClientStore(dymintCounterpartyChain.GetContext(), endpoint.ClientID) // Call ZeroCustomFields on upgraded clients to clear any client-chosen parameters in test-case upgradedClient upgradedClient = upgradedClient.ZeroCustomFields() clientState, consensusState, err := cs.VerifyUpgradeAndUpdateState( - suite.chainA.GetContext(), + dymintCounterpartyChain.GetContext(), suite.cdc, clientStore, upgradedClient, diff --git a/testing/chain.go b/testing/chain.go index 8897550d922..291bdb0f507 100644 --- a/testing/chain.go +++ b/testing/chain.go @@ -438,6 +438,8 @@ func (chain *TestChain) ConstructUpdateClientHeader(counterparty *TestChain, cli switch counterparty.TestChainClient.GetSelfClientType() { case exported.Tendermint: return ConstructUpdateTMClientHeaderWithTrustedHeight(counterparty, clientID, trustedHeight) + case exported.Dymint: + return ConstructUpdateDMClientHeaderWithTrustedHeight(counterparty, clientID, trustedHeight) default: panic(fmt.Sprintf("client type %s is not supported", counterparty.TestChainClient.GetSelfClientType())) } diff --git a/testing/chain_dymint.go b/testing/chain_dymint.go index cef0c20f04c..df5f1ce59eb 100644 --- a/testing/chain_dymint.go +++ b/testing/chain_dymint.go @@ -71,6 +71,10 @@ func NewChainDymintClient(tc *TestChain) *TestChainDymint { return chain } +func (chain *TestChainDymint) GetSelfClientType() string { + return exported.Dymint +} + func (chain *TestChainDymint) NewConfig() ClientConfig { return &DymintConfig{ TrustLevel: DefaultDymintTrustLevel, @@ -111,27 +115,12 @@ func (chain *TestChainDymint) NextBlock() { chain.BeginBlock() } -// ConstructUpdateClientHeader will construct a valid 01-dymint Header to update the -// light client on the source chain. -func (chain *TestChainDymint) ConstructUpdateClientHeader(counterparty *TestChain, clientID string) (exported.Header, error) { - return chain.ConstructUpdateDMClientHeader(counterparty, clientID) -} - // ConstructUpdateDMClientHeader will construct a valid 01-dymint Header to update the // light client on the source chain. -func (chain *TestChainDymint) ConstructUpdateDMClientHeader(counterparty *TestChain, clientID string) (*ibcdmtypes.Header, error) { - return chain.ConstructUpdateDMClientHeaderWithTrustedHeight(counterparty, clientID, clienttypes.ZeroHeight()) -} - -// ConstructUpdateDMClientHeader will construct a valid 01-dymint Header to update the -// light client on the source chain. -func (chain *TestChainDymint) ConstructUpdateDMClientHeaderWithTrustedHeight(counterparty *TestChain, clientID string, trustedHeight clienttypes.Height) (*ibcdmtypes.Header, error) { +func ConstructUpdateDMClientHeaderWithTrustedHeight(counterparty *TestChain, clientID string, trustedHeight clienttypes.Height) (*ibcdmtypes.Header, error) { counterpartyTestChainDymint := counterparty.TestChainClient.(*TestChainDymint) header := counterpartyTestChainDymint.LastHeader - // Relayer must query for LatestHeight on client to get TrustedHeight if the trusted height is not set - if trustedHeight.IsZero() { - trustedHeight = chain.TC.GetClientState(clientID).GetLatestHeight().(clienttypes.Height) - } + var ( tmTrustedVals *tmtypes.ValidatorSet ok bool From 2220c9a7f8f04d33a1231c2aef64a9385f5af823 Mon Sep 17 00:00:00 2001 From: Lior Zilpa Date: Wed, 23 Nov 2022 11:47:52 +0200 Subject: [PATCH 115/140] * remove validator set checking * remove TrustLevel & UnbondingPeriod * The TrustingPeriod is used only for check for expaired states [IsExpired] --- .../01-dymint/types/client_state.go | 28 +---- .../01-dymint/types/client_state_test.go | 45 +++---- .../01-dymint/types/misbehaviour_handle.go | 31 ++--- .../types/misbehaviour_handle_test.go | 113 +++--------------- .../01-dymint/types/self_client.go | 16 +-- .../light-clients/01-dymint/types/update.go | 66 +++++----- .../01-dymint/types/update_test.go | 81 +++---------- .../light-clients/01-dymint/types/upgrade.go | 2 +- .../01-dymint/types/upgrade_test.go | 12 +- testing/chain_dymint.go | 6 +- 10 files changed, 111 insertions(+), 289 deletions(-) diff --git a/modules/light-clients/01-dymint/types/client_state.go b/modules/light-clients/01-dymint/types/client_state.go index 37cbaa7a1aa..182bfd6d630 100644 --- a/modules/light-clients/01-dymint/types/client_state.go +++ b/modules/light-clients/01-dymint/types/client_state.go @@ -8,7 +8,6 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/tendermint/tendermint/light" tmtypes "github.com/tendermint/tendermint/types" clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" @@ -23,16 +22,14 @@ var _ exported.ClientState = (*ClientState)(nil) // NewClientState creates a new ClientState instance func NewClientState( - chainID string, trustLevel Fraction, - trustingPeriod, ubdPeriod, maxClockDrift time.Duration, + chainID string, + trustingPeriod, maxClockDrift time.Duration, latestHeight clienttypes.Height, specs []*ics23.ProofSpec, upgradePath []string, allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour bool, ) *ClientState { return &ClientState{ ChainId: chainID, - TrustLevel: trustLevel, TrustingPeriod: trustingPeriod, - UnbondingPeriod: ubdPeriod, MaxClockDrift: maxClockDrift, LatestHeight: latestHeight, FrozenHeight: clienttypes.ZeroHeight(), @@ -112,15 +109,9 @@ func (cs ClientState) Validate() error { return sdkerrors.Wrapf(ErrInvalidChainID, "chainID is too long; got: %d, max: %d", len(cs.ChainId), tmtypes.MaxChainIDLen) } - if err := light.ValidateTrustLevel(cs.TrustLevel.ToDymint()); err != nil { - return err - } if cs.TrustingPeriod == 0 { return sdkerrors.Wrap(ErrInvalidTrustingPeriod, "trusting period cannot be zero") } - if cs.UnbondingPeriod == 0 { - return sdkerrors.Wrap(ErrInvalidUnbondingPeriod, "unbonding period cannot be zero") - } if cs.MaxClockDrift == 0 { return sdkerrors.Wrap(ErrInvalidMaxClockDrift, "max clock drift cannot be zero") } @@ -133,12 +124,6 @@ func (cs ClientState) Validate() error { if cs.LatestHeight.RevisionHeight == 0 { return sdkerrors.Wrapf(ErrInvalidHeaderHeight, "dymint client's latest height revision height cannot be zero") } - if cs.TrustingPeriod >= cs.UnbondingPeriod { - return sdkerrors.Wrapf( - ErrInvalidTrustingPeriod, - "trusting period (%s) should be < unbonding period (%s)", cs.TrustingPeriod, cs.UnbondingPeriod, - ) - } if cs.ProofSpecs == nil { return sdkerrors.Wrap(ErrInvalidProofSpecs, "proof specs cannot be nil for tm client") @@ -170,11 +155,10 @@ func (cs ClientState) ZeroCustomFields() exported.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, + ChainId: cs.ChainId, + LatestHeight: cs.LatestHeight, + ProofSpecs: cs.ProofSpecs, + UpgradePath: cs.UpgradePath, } } diff --git a/modules/light-clients/01-dymint/types/client_state_test.go b/modules/light-clients/01-dymint/types/client_state_test.go index f85e3b1f1a6..e5c2e8588b6 100644 --- a/modules/light-clients/01-dymint/types/client_state_test.go +++ b/modules/light-clients/01-dymint/types/client_state_test.go @@ -91,17 +91,17 @@ func (suite *DymintTestSuite) TestValidate() { }{ { name: "valid client", - clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + clientState: types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), expPass: true, }, { name: "valid client with nil upgrade path", - clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), nil, false, false), + clientState: types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), nil, false, false), expPass: true, }, { name: "invalid chainID", - clientState: types.NewClientState(" ", types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + clientState: types.NewClientState(" ", trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), expPass: false, }, { @@ -109,7 +109,7 @@ func (suite *DymintTestSuite) TestValidate() { // Do not only fix the test, fix the code! // https://github.com/cosmos/ibc-go/issues/177 name: "valid chainID - chainID validation failed for chainID of length 50! ", - clientState: types.NewClientState(fiftyCharChainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + clientState: types.NewClientState(fiftyCharChainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), expPass: true, }, { @@ -117,52 +117,37 @@ func (suite *DymintTestSuite) TestValidate() { // Do not only fix the test, fix the code! // https://github.com/cosmos/ibc-go/issues/177 name: "invalid chainID - chainID validation did not fail for chainID of length 51! ", - clientState: types.NewClientState(fiftyOneCharChainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), - expPass: false, - }, - { - name: "invalid trust level", - clientState: types.NewClientState(chainID, types.Fraction{Numerator: 0, Denominator: 1}, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + clientState: types.NewClientState(fiftyOneCharChainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), expPass: false, }, { name: "invalid trusting period", - clientState: types.NewClientState(chainID, types.DefaultTrustLevel, 0, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), - expPass: false, - }, - { - name: "invalid unbonding period", - clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, 0, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + clientState: types.NewClientState(chainID, 0, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), expPass: false, }, { name: "invalid max clock drift", - clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, 0, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + clientState: types.NewClientState(chainID, trustingPeriod, 0, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), expPass: false, }, { name: "invalid revision number", - clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + clientState: types.NewClientState(chainID, trustingPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), upgradePath, false, false), expPass: false, }, { name: "invalid revision height", - clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.ZeroHeight(), commitmenttypes.GetSDKSpecs(), upgradePath, false, false), - expPass: false, - }, - { - name: "trusting period not less than unbonding period", - clientState: types.NewClientState(chainID, types.DefaultTrustLevel, ubdPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + clientState: types.NewClientState(chainID, trustingPeriod, maxClockDrift, clienttypes.ZeroHeight(), commitmenttypes.GetSDKSpecs(), upgradePath, false, false), expPass: false, }, { name: "proof specs is nil", - clientState: types.NewClientState(chainID, types.DefaultTrustLevel, ubdPeriod, ubdPeriod, maxClockDrift, height, nil, upgradePath, false, false), + clientState: types.NewClientState(chainID, ubdPeriod, maxClockDrift, height, nil, upgradePath, false, false), expPass: false, }, { name: "proof specs contains nil", - clientState: types.NewClientState(chainID, types.DefaultTrustLevel, ubdPeriod, ubdPeriod, maxClockDrift, height, []*ics23.ProofSpec{ics23.TendermintSpec, nil}, upgradePath, false, false), + clientState: types.NewClientState(chainID, ubdPeriod, maxClockDrift, height, []*ics23.ProofSpec{ics23.TendermintSpec, nil}, upgradePath, false, false), expPass: false, }, } @@ -237,7 +222,7 @@ func (suite *DymintTestSuite) TestVerifyClientConsensusState() { // FIXME: uncomment // { // name: "successful verification", - // clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), + // clientState: types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs()), // consensusState: types.ConsensusState{ // Root: commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), // }, @@ -246,7 +231,7 @@ func (suite *DymintTestSuite) TestVerifyClientConsensusState() { // }, { name: "ApplyPrefix failed", - clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + clientState: types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), consensusState: &types.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), }, @@ -255,7 +240,7 @@ func (suite *DymintTestSuite) TestVerifyClientConsensusState() { }, { name: "latest client height < height", - clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + clientState: types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), consensusState: &types.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), }, @@ -264,7 +249,7 @@ func (suite *DymintTestSuite) TestVerifyClientConsensusState() { }, { name: "proof verification failed", - clientState: types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + clientState: types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), consensusState: &types.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), NextValidatorsHash: suite.valsHash, diff --git a/modules/light-clients/01-dymint/types/misbehaviour_handle.go b/modules/light-clients/01-dymint/types/misbehaviour_handle.go index ef4b829a382..efdcaeb3e2b 100644 --- a/modules/light-clients/01-dymint/types/misbehaviour_handle.go +++ b/modules/light-clients/01-dymint/types/misbehaviour_handle.go @@ -2,6 +2,7 @@ package types import ( "bytes" + "strings" "time" "github.com/cosmos/cosmos-sdk/codec" @@ -102,21 +103,16 @@ func checkMisbehaviourHeader( clientState *ClientState, consState *ConsensusState, header *Header, currentTimestamp time.Time, ) error { - tmTrustedValset, err := tmtypes.ValidatorSetFromProto(header.TrustedValidators) + _, err := tmtypes.ValidatorSetFromProto(header.TrustedValidators) if err != nil { return sdkerrors.Wrap(err, "trusted validator set is not dymint validator set type") } - tmCommit, err := tmtypes.CommitFromProto(header.Commit) + _, err = tmtypes.CommitFromProto(header.Commit) if err != nil { return sdkerrors.Wrap(err, "commit is not dymint 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 sdkerrors.Wrapf( @@ -126,19 +122,16 @@ func checkMisbehaviourHeader( ) } - chainID := clientState.GetChainID() - // If chainID is in revision format, then set revision number of chainID with the revision number - // of the misbehaviour header - if clienttypes.IsRevisionFormat(chainID) { - chainID, _ = clienttypes.SetRevisionNumber(chainID, header.GetHeight().GetRevisionNumber()) - } + // swap out revision if exists and get chainID + clientStateChainID := strings.Split(clientState.GetChainID(), "-")[0] + headerChainID := strings.Split(header.Header.ChainID, "-")[0] - // - 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.ToDymint(), - ); err != nil { - return sdkerrors.Wrapf(clienttypes.ErrInvalidMisbehaviour, "validator set in header has too much change from trusted validator set: %v", err) + if headerChainID != clientStateChainID { + return sdkerrors.Wrapf( + ErrInvalidChainID, + "expected %s{chainID}, got %s", clientStateChainID, headerChainID, + ) } + return nil } diff --git a/modules/light-clients/01-dymint/types/misbehaviour_handle_test.go b/modules/light-clients/01-dymint/types/misbehaviour_handle_test.go index 7b2ad331acd..bec054db940 100644 --- a/modules/light-clients/01-dymint/types/misbehaviour_handle_test.go +++ b/modules/light-clients/01-dymint/types/misbehaviour_handle_test.go @@ -26,16 +26,12 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { // Create bothValSet with both suite validator and altVal bothValSet := tmtypes.NewValidatorSet(append(suite.valSet.Validators, altVal)) bothValsHash := bothValSet.Hash() - // Create alternative validator set with only altVal - altValSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{altVal}) _, suiteVal := suite.valSet.GetByIndex(0) // Create signer array and ensure it is in same order as bothValSet bothSigners := ibctesting.CreateSortedSignerArray(altPrivVal, suite.privVal, altVal, suiteVal) - altSigners := []tmtypes.PrivValidator{altPrivVal} - heightMinus1 := clienttypes.NewHeight(height.RevisionNumber, height.RevisionHeight-1) heightMinus3 := clienttypes.NewHeight(height.RevisionNumber, height.RevisionHeight-3) @@ -58,7 +54,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }{ { "valid fork misbehaviour", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), @@ -73,7 +69,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "valid time misbehaviour", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), @@ -88,7 +84,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "valid time misbehaviour header 1 stricly less than header 2", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), @@ -103,7 +99,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "valid misbehavior at height greater than last consensusState", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), heightMinus1, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), @@ -118,7 +114,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "valid misbehaviour with different trusted heights", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), heightMinus1, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), @@ -133,7 +129,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "valid misbehaviour at a previous revision", - types.NewClientState(chainIDRevision1, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainIDRevision1, trustingPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), upgradePath, false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), heightMinus1, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), @@ -148,7 +144,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "valid misbehaviour at a future revision", - types.NewClientState(chainIDRevision0, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainIDRevision0, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), heightMinus1, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), @@ -163,7 +159,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "valid misbehaviour with trusted heights at a previous revision", - types.NewClientState(chainIDRevision1, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainIDRevision1, trustingPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), upgradePath, false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), heightMinus1, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), @@ -178,7 +174,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "consensus state's valset hash different from misbehaviour should still pass", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), height, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), @@ -193,7 +189,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "invalid fork misbehaviour: identical headers", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), @@ -208,7 +204,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "invalid time misbehaviour: monotonically increasing time", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), @@ -223,7 +219,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "invalid misbehavior misbehaviour from different chain", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), @@ -238,7 +234,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "invalid misbehavior misbehaviour with trusted height different from trusted consensus state", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), heightMinus1, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), @@ -251,21 +247,6 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { suite.now, false, }, - { - "invalid misbehavior misbehaviour with trusted validators different from trusted consensus state", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), - types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), - heightMinus1, - types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), - heightMinus3, - &types.Misbehaviour{ - Header1: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus1, suite.now, bothValSet, bothValSet, bothSigners), - Header2: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), heightMinus3, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), - ClientId: chainID, - }, - suite.now, - false, - }, { "already frozen client state", &types.ClientState{FrozenHeight: clienttypes.NewHeight(0, 1)}, @@ -283,7 +264,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "trusted consensus state does not exist", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), nil, // consensus state for trusted height - 1 does not exist in store clienttypes.Height{}, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), @@ -298,7 +279,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "invalid dymint misbehaviour", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), @@ -309,7 +290,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "provided height > header height", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), @@ -324,7 +305,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "trusting period expired", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), types.NewConsensusState(time.Time{}, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), heightMinus1, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), @@ -337,66 +318,6 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { suite.now.Add(trustingPeriod), false, }, - { - "trusted validators is incorrect for given consensus state", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), - types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), - height, - types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), - height, - &types.Misbehaviour{ - Header1: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, suite.valSet, bothSigners), - Header2: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, suite.valSet, bothSigners), - ClientId: chainID, - }, - suite.now, - false, - }, - { - "first valset has too much change", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), - types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), - height, - types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), - height, - &types.Misbehaviour{ - Header1: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, altValSet, bothValSet, altSigners), - Header2: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), bothValSet, bothValSet, bothSigners), - ClientId: chainID, - }, - suite.now, - false, - }, - { - "second valset has too much change", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), - types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), - height, - types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), - height, - &types.Misbehaviour{ - Header1: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, bothValSet, bothValSet, bothSigners), - Header2: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), altValSet, bothValSet, altSigners), - ClientId: chainID, - }, - suite.now, - false, - }, - { - "both valsets have too much change", - types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), - types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), - height, - types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), - height, - &types.Misbehaviour{ - Header1: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now, altValSet, bothValSet, altSigners), - Header2: chainDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight+1), height, suite.now.Add(time.Minute), altValSet, bothValSet, altSigners), - ClientId: chainID, - }, - suite.now, - false, - }, } for i, tc := range testCases { diff --git a/modules/light-clients/01-dymint/types/self_client.go b/modules/light-clients/01-dymint/types/self_client.go index 6f8967525cd..e5b7db85075 100644 --- a/modules/light-clients/01-dymint/types/self_client.go +++ b/modules/light-clients/01-dymint/types/self_client.go @@ -7,7 +7,6 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/tendermint/tendermint/light" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" @@ -28,6 +27,7 @@ func NewSelfClient() exported.SelfClient { // ValidateSelfClientState validates the client parameters for a client of the running chain // This function is only used to validate the client state the counterparty stores for this chain // Client must be in same revision as the executing chain +// dymint doesn't care about the unbonding period, so ignore it func (sc SelfClient) ValidateSelfClientState( ctx sdk.Context, expectedUbdPeriod time.Duration, @@ -68,20 +68,6 @@ func (sc SelfClient) ValidateSelfClientState( expectedProofSpecs, tmClient.ProofSpecs) } - if err := light.ValidateTrustLevel(tmClient.TrustLevel.ToDymint()); err != nil { - return sdkerrors.Wrapf(clienttypes.ErrInvalidClient, "trust-level invalid: %v", err) - } - - if expectedUbdPeriod != tmClient.UnbondingPeriod { - return sdkerrors.Wrapf(clienttypes.ErrInvalidClient, "invalid unbonding period. expected: %s, got: %s", - expectedUbdPeriod, tmClient.UnbondingPeriod) - } - - if tmClient.UnbondingPeriod < tmClient.TrustingPeriod { - return sdkerrors.Wrapf(clienttypes.ErrInvalidClient, "unbonding period must be greater than trusting period. unbonding period (%d) < trusting period (%d)", - tmClient.UnbondingPeriod, tmClient.TrustingPeriod) - } - if len(tmClient.UpgradePath) != 0 { // For now, SDK IBC implementation assumes that upgrade path (if defined) is defined by SDK upgrade module expectedUpgradePath := []string{upgradetypes.StoreKey, upgradetypes.KeyUpgradedIBCState} diff --git a/modules/light-clients/01-dymint/types/update.go b/modules/light-clients/01-dymint/types/update.go index 033bd114050..1f1690e2b20 100644 --- a/modules/light-clients/01-dymint/types/update.go +++ b/modules/light-clients/01-dymint/types/update.go @@ -1,7 +1,7 @@ package types import ( - "bytes" + fmt "fmt" "reflect" "time" @@ -142,23 +142,40 @@ func (cs ClientState) CheckHeaderAndUpdateState( return newClientState, consensusState, nil } -// checkTrustedHeader checks that consensus state matches trusted fields of Header -func checkTrustedHeader(header *Header, consState *ConsensusState) error { - tmTrustedValidators, err := tmtypes.ValidatorSetFromProto(header.TrustedValidators) - if err != nil { - return sdkerrors.Wrap(err, "trusted validator set in not dymint validator set type") +func _verifyNewHeaderAndVals( + trustedHeader *tmtypes.SignedHeader, + untrustedHeader *tmtypes.SignedHeader, + trustingPeriod time.Duration, + now time.Time, + maxClockDrift time.Duration) error { + + if light.HeaderExpired(trustedHeader, trustingPeriod, now) { + return light.ErrOldHeaderExpired{At: trustedHeader.Time.Add(trustingPeriod), Now: now} } - // 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 sdkerrors.Wrapf( - ErrInvalidValidatorSet, - "trusted validators %s, does not hash to latest trusted validators. Expected: %X, got: %X", - header.TrustedValidators, consState.NextValidatorsHash, tvalHash, - ) + if err := untrustedHeader.ValidateBasic(trustedHeader.ChainID); err != nil { + return fmt.Errorf("untrustedHeader.ValidateBasic failed: %w", err) + } + + if untrustedHeader.Height <= trustedHeader.Height { + return fmt.Errorf("expected new header height %d to be greater than one of old header %d", + untrustedHeader.Height, + trustedHeader.Height) + } + + if !untrustedHeader.Time.After(trustedHeader.Time) { + return fmt.Errorf("expected new header time %v to be after old header time %v", + untrustedHeader.Time, + trustedHeader.Time) + } + + if !untrustedHeader.Time.Before(now.Add(maxClockDrift)) { + return fmt.Errorf("new header has a time from the future %v (now: %v; max clock drift: %v)", + untrustedHeader.Time, + now, + maxClockDrift) } + return nil } @@ -168,9 +185,6 @@ func checkValidity( clientState *ClientState, consState *ConsensusState, header *Header, currentTimestamp time.Time, ) error { - 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 @@ -182,21 +196,11 @@ func checkValidity( ) } - tmTrustedValidators, err := tmtypes.ValidatorSetFromProto(header.TrustedValidators) - if err != nil { - return sdkerrors.Wrap(err, "trusted validator set in not dymint validator set type") - } - tmSignedHeader, err := tmtypes.SignedHeaderFromProto(header.SignedHeader) if err != nil { return sdkerrors.Wrap(err, "signed header in not dymint signed header type") } - tmValidatorSet, err := tmtypes.ValidatorSetFromProto(header.ValidatorSet) - if err != nil { - return sdkerrors.Wrap(err, "validator set in not dymint validator set type") - } - // assert header height is newer than consensus state if header.GetHeight().LTE(header.TrustedHeight) { return sdkerrors.Wrapf( @@ -233,10 +237,10 @@ func checkValidity( // - 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( + err = _verifyNewHeaderAndVals( &signedHeader, - tmTrustedValidators, tmSignedHeader, tmValidatorSet, - clientState.TrustingPeriod, currentTimestamp, clientState.MaxClockDrift, clientState.TrustLevel.ToDymint(), + tmSignedHeader, + clientState.TrustingPeriod, currentTimestamp, clientState.MaxClockDrift, ) if err != nil { return sdkerrors.Wrap(err, "failed to verify header") diff --git a/modules/light-clients/01-dymint/types/update_test.go b/modules/light-clients/01-dymint/types/update_test.go index 42c66b42383..873e41b38a2 100644 --- a/modules/light-clients/01-dymint/types/update_test.go +++ b/modules/light-clients/01-dymint/types/update_test.go @@ -40,9 +40,6 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { heightPlus5 := clienttypes.NewHeight(height.RevisionNumber, height.RevisionHeight+5) altVal := tmtypes.NewValidator(altPubKey, revisionHeight) - // Create alternative validator set with only altVal, invalid update (too much change in valSet) - altValSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{altVal}) - altSigners := []tmtypes.PrivValidator{altPrivVal} var chainADymint *ibctesting.TestChainDymint @@ -55,7 +52,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "successful update with next height and same validator set", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) currentTime = suite.now @@ -66,7 +63,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "successful update with future height and different validator set", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus5.RevisionHeight), height, suite.headerTime, bothValSet, suite.valSet, bothSigners) currentTime = suite.now @@ -77,7 +74,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "successful update with next height and different validator set", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), bothValSet.Hash()) newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, bothValSet, bothValSet, bothSigners) currentTime = suite.now @@ -88,7 +85,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "successful update for a previous height", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) consStateHeight = heightMinus3 newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightMinus1.RevisionHeight), heightMinus3, suite.headerTime, bothValSet, suite.valSet, bothSigners) @@ -100,7 +97,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "successful update for a previous revision", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainIDRevision1, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainIDRevision1, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) consStateHeight = heightMinus3 newHeader = chainADymint.CreateDMClientHeader(chainIDRevision0, int64(height.RevisionHeight), heightMinus3, suite.headerTime, bothValSet, suite.valSet, bothSigners) @@ -111,7 +108,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "successful update with identical header to a previous update", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, heightPlus1, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, heightPlus1, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) currentTime = suite.now @@ -125,7 +122,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "misbehaviour detection: header conflicts with existing consensus state", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, heightPlus1, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, heightPlus1, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) currentTime = suite.now @@ -141,7 +138,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "misbehaviour detection: previous consensus state time is not before header time. time monotonicity violation", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) // create an intermediate consensus state with the same time as the newHeader to create a time violation. // header time is after client time consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) @@ -159,7 +156,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "misbehaviour detection: next consensus state time is not after header time. time monotonicity violation", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) // create the next consensus state with the same time as the intermediate newHeader to create a time violation. // header time is after clientTime consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) @@ -177,7 +174,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "unsuccessful update with incorrect header chain-id", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) newHeader = chainADymint.CreateDMClientHeader("ethermint", int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) currentTime = suite.now @@ -188,7 +185,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "unsuccessful update to a future revision", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainIDRevision0, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainIDRevision0, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) newHeader = chainADymint.CreateDMClientHeader(chainIDRevision1, 1, height, suite.headerTime, suite.valSet, suite.valSet, signers) currentTime = suite.now @@ -198,7 +195,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "unsuccessful update: header height revision and trusted height revision mismatch", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainIDRevision1, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainIDRevision1, trustingPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) newHeader = chainADymint.CreateDMClientHeader(chainIDRevision1, 3, height, suite.headerTime, suite.valSet, suite.valSet, signers) currentTime = suite.now @@ -206,54 +203,10 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { expFrozen: false, expPass: false, }, - { - name: "unsuccessful update with next height: update header mismatches nextValSetHash", - setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) - consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, bothValSet, suite.valSet, bothSigners) - currentTime = suite.now - }, - expFrozen: false, - expPass: false, - }, - { - name: "unsuccessful update with next height: update header mismatches different nextValSetHash", - setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) - consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), bothValSet.Hash()) - newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, bothValSet, signers) - currentTime = suite.now - }, - expFrozen: false, - expPass: false, - }, - { - name: "unsuccessful update with future height: too much change in validator set", - setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) - consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus5.RevisionHeight), height, suite.headerTime, altValSet, suite.valSet, altSigners) - currentTime = suite.now - }, - expFrozen: false, - expPass: false, - }, - { - name: "unsuccessful updates, passed in incorrect trusted validators for given consensus state", - setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) - consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) - newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus5.RevisionHeight), height, suite.headerTime, bothValSet, bothValSet, bothSigners) - currentTime = suite.now - }, - expFrozen: false, - expPass: false, - }, { name: "unsuccessful update: trusting period has passed since last client timestamp", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) // make current time pass trusting period from last timestamp on clientstate @@ -265,7 +218,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "unsuccessful update: header timestamp is past current timestamp", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers) currentTime = suite.now @@ -276,7 +229,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "unsuccessful update: header timestamp is not past last client timestamp", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.clientTime, suite.valSet, suite.valSet, signers) currentTime = suite.now @@ -287,7 +240,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "header basic validation failed", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) // cause new header to fail validatebasic by changing commit height to mismatch header height @@ -300,7 +253,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "header height < consensus height", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.NewHeight(height.RevisionNumber, heightPlus5.RevisionHeight), commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, clienttypes.NewHeight(height.RevisionNumber, heightPlus5.RevisionHeight), commitmenttypes.GetSDKSpecs(), upgradePath, false, false) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) // Make new header at height less than latest client state newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightMinus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) diff --git a/modules/light-clients/01-dymint/types/upgrade.go b/modules/light-clients/01-dymint/types/upgrade.go index bcc3966cfa4..2202a6dbd5f 100644 --- a/modules/light-clients/01-dymint/types/upgrade.go +++ b/modules/light-clients/01-dymint/types/upgrade.go @@ -99,7 +99,7 @@ func (cs ClientState) VerifyUpgradeAndUpdateState( // All chain-chosen parameters come from committed client, all client-chosen parameters // come from current client. newClientState := NewClientState( - tmUpgradeClient.ChainId, cs.TrustLevel, cs.TrustingPeriod, tmUpgradeClient.UnbondingPeriod, + tmUpgradeClient.ChainId, cs.TrustingPeriod, cs.MaxClockDrift, tmUpgradeClient.LatestHeight, tmUpgradeClient.ProofSpecs, tmUpgradeClient.UpgradePath, cs.AllowUpdateAfterExpiry, cs.AllowUpdateAfterMisbehaviour, ) diff --git a/modules/light-clients/01-dymint/types/upgrade_test.go b/modules/light-clients/01-dymint/types/upgrade_test.go index bea049a5c3d..8a3d8938f0e 100644 --- a/modules/light-clients/01-dymint/types/upgrade_test.go +++ b/modules/light-clients/01-dymint/types/upgrade_test.go @@ -61,7 +61,7 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { setup: func() { upgradedHeight := clienttypes.NewHeight(0, uint64(dymintChain.GetContext().BlockHeight()+2)) // don't use -1 suffix in chain id - upgradedClient = types.NewClientState("newChainId", types.DefaultTrustLevel, trustingPeriod, ubdPeriod+trustingPeriod, maxClockDrift, upgradedHeight, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + upgradedClient = types.NewClientState("newChainId", trustingPeriod, maxClockDrift, upgradedHeight, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) upgradedClient = upgradedClient.ZeroCustomFields() upgradedClientBz, err = clienttypes.MarshalClientState(dymintCounterpartyChain.App.AppCodec(), upgradedClient) suite.Require().NoError(err) @@ -116,7 +116,7 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { name: "unsuccessful upgrade: committed client does not have zeroed custom fields", setup: func() { // non-zeroed upgrade client - upgradedClient = types.NewClientState(newChainId, types.DefaultTrustLevel, trustingPeriod, ubdPeriod+trustingPeriod, maxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + upgradedClient = types.NewClientState(newChainId, trustingPeriod, maxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) upgradedClientBz, err = clienttypes.MarshalClientState(dymintCounterpartyChain.App.AppCodec(), upgradedClient) suite.Require().NoError(err) @@ -152,7 +152,7 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) // change upgradedClient client-specified parameters - upgradedClient = types.NewClientState("wrongchainID", types.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), upgradePath, true, true) + upgradedClient = types.NewClientState("wrongchainID", trustingPeriod, maxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), upgradePath, true, true) suite.coordinator.CommitBlock(dymintChain) err := endpoint.UpdateClient() @@ -174,7 +174,7 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) // change upgradedClient client-specified parameters - upgradedClient = types.NewClientState(newChainId, types.DefaultTrustLevel, ubdPeriod, ubdPeriod+trustingPeriod, maxClockDrift+5, lastHeight, commitmenttypes.GetSDKSpecs(), upgradePath, true, false) + upgradedClient = types.NewClientState(newChainId, ubdPeriod+trustingPeriod, maxClockDrift+5, lastHeight, commitmenttypes.GetSDKSpecs(), upgradePath, true, false) suite.coordinator.CommitBlock(dymintChain) err := endpoint.UpdateClient() @@ -405,7 +405,7 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { 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 = types.NewClientState(newChainId, types.DefaultTrustLevel, trustingPeriod, trustingPeriod, maxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + upgradedClient = types.NewClientState(newChainId, trustingPeriod, maxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) upgradedClientBz, err = clienttypes.MarshalClientState(dymintCounterpartyChain.App.AppCodec(), upgradedClient) suite.Require().NoError(err) @@ -451,7 +451,7 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { endpoint = path.EndpointA } - upgradedClient = types.NewClientState(newChainId, types.DefaultTrustLevel, trustingPeriod, ubdPeriod+trustingPeriod, maxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + upgradedClient = types.NewClientState(newChainId, trustingPeriod, maxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) upgradedClient = upgradedClient.ZeroCustomFields() upgradedClientBz, err = clienttypes.MarshalClientState(dymintCounterpartyChain.App.AppCodec(), upgradedClient) suite.Require().NoError(err) diff --git a/testing/chain_dymint.go b/testing/chain_dymint.go index df5f1ce59eb..6378f2b3c17 100644 --- a/testing/chain_dymint.go +++ b/testing/chain_dymint.go @@ -25,9 +25,7 @@ var ( ) type DymintConfig struct { - TrustLevel ibcdmtypes.Fraction TrustingPeriod time.Duration - UnbondingPeriod time.Duration MaxClockDrift time.Duration AllowUpdateAfterExpiry bool AllowUpdateAfterMisbehaviour bool @@ -77,9 +75,7 @@ func (chain *TestChainDymint) GetSelfClientType() string { func (chain *TestChainDymint) NewConfig() ClientConfig { return &DymintConfig{ - TrustLevel: DefaultDymintTrustLevel, TrustingPeriod: TrustingPeriod, - UnbondingPeriod: UnbondingPeriod, MaxClockDrift: MaxClockDrift, AllowUpdateAfterExpiry: false, AllowUpdateAfterMisbehaviour: false, @@ -236,7 +232,7 @@ func (chain *TestChainDymint) ClientConfigToState(clientConfig ClientConfig) exp height := chain.LastHeader.GetHeight().(clienttypes.Height) clientState := ibcdmtypes.NewClientState( - chain.TC.ChainID, tmConfig.TrustLevel, tmConfig.TrustingPeriod, tmConfig.UnbondingPeriod, tmConfig.MaxClockDrift, + chain.TC.ChainID, tmConfig.TrustingPeriod, tmConfig.MaxClockDrift, height, commitmenttypes.GetSDKSpecs(), UpgradePath, tmConfig.AllowUpdateAfterExpiry, tmConfig.AllowUpdateAfterMisbehaviour, ) return clientState From 7fe391176826ef497479b7dacd90313b2cbd2cf9 Mon Sep 17 00:00:00 2001 From: Lior Zilpa Date: Wed, 23 Nov 2022 17:20:55 +0200 Subject: [PATCH 116/140] enhance Validate Commit to check the commit and the proposer signatures & move the function to the header object --- .../light-clients/01-dymint/types/header.go | 45 +++++++++++++++++++ .../01-dymint/types/misbehaviour.go | 37 ++------------- .../light-clients/01-dymint/types/update.go | 3 ++ .../01-dymint/types/update_test.go | 37 +++++++++++++++ 4 files changed, 88 insertions(+), 34 deletions(-) diff --git a/modules/light-clients/01-dymint/types/header.go b/modules/light-clients/01-dymint/types/header.go index 3e73b5562a0..5baa3d65187 100644 --- a/modules/light-clients/01-dymint/types/header.go +++ b/modules/light-clients/01-dymint/types/header.go @@ -2,6 +2,7 @@ package types import ( "bytes" + fmt "fmt" "time" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -80,3 +81,47 @@ func (h Header) ValidateBasic() error { } return nil } + +// validCommit checks if the given commit is a valid commit from the passed-in validatorset +func (h Header) ValidateCommit() (err error) { + chainID := h.Header.ChainID + blockID, err := tmtypes.BlockIDFromProto(&h.SignedHeader.Commit.BlockID) + if err != nil { + return sdkerrors.Wrap(err, "invalid block ID from header SignedHeader.Commit") + } + tmCommit, err := tmtypes.CommitFromProto(h.Commit) + if err != nil { + return sdkerrors.Wrap(err, "commit is not dymint commit type") + } + tmValset, err := tmtypes.ValidatorSetFromProto(h.ValidatorSet) + if err != nil { + return sdkerrors.Wrap(err, "validator set is not dymint validator set type") + } + + if !blockID.Equals(tmCommit.BlockID) { + return fmt.Errorf("invalid commit -- wrong block ID: want %v, got %v", + blockID, tmCommit.BlockID) + } + + // We don't know the validators that committed this block, so we have to + // check for each vote if its validator is already known. + valIdx, val := tmValset.GetByAddress(h.Header.ProposerAddress) + if val != nil { + commitSig := tmCommit.Signatures[valIdx] + if !commitSig.ForBlock() { + return sdkerrors.Wrap(clienttypes.ErrInvalidHeader, "validator set did not commit to header") + } + // Validate signature. + voteSignBytes := tmCommit.VoteSignBytes(chainID, valIdx) + if !bytes.Equal(commitSig.ValidatorAddress, h.Header.ProposerAddress) { + return fmt.Errorf("wrong proposer address in commit, got %X) but expected %X", valIdx, h.Header.ProposerAddress) + } + if !val.PubKey.VerifySignature(voteSignBytes, commitSig.Signature) { + return fmt.Errorf("wrong signature (#%d): %X", valIdx, commitSig.Signature) + } + } else { + return sdkerrors.Wrap(clienttypes.ErrInvalidHeader, "validator set did not commit to header") + } + + return nil +} diff --git a/modules/light-clients/01-dymint/types/misbehaviour.go b/modules/light-clients/01-dymint/types/misbehaviour.go index 8bfbfe87b26..e99bccaf99a 100644 --- a/modules/light-clients/01-dymint/types/misbehaviour.go +++ b/modules/light-clients/01-dymint/types/misbehaviour.go @@ -4,8 +4,6 @@ import ( "time" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - tmtypes "github.com/tendermint/tendermint/types" clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" host "github.com/cosmos/ibc-go/v3/modules/core/24-host" @@ -14,7 +12,7 @@ import ( var _ exported.Misbehaviour = &Misbehaviour{} -// Use the same FrozenHeight for all misbehaviour +// FrozenHeight - Use the same FrozenHeight for all misbehaviour var FrozenHeight = clienttypes.NewHeight(0, 1) // NewMisbehaviour creates a new Misbehaviour instance. @@ -93,40 +91,11 @@ func (misbehaviour Misbehaviour) ValidateBasic() error { return sdkerrors.Wrapf(clienttypes.ErrInvalidMisbehaviour, "Header1 height is less than Header2 height (%s < %s)", misbehaviour.Header1.GetHeight(), misbehaviour.Header2.GetHeight()) } - blockID1, err := tmtypes.BlockIDFromProto(&misbehaviour.Header1.SignedHeader.Commit.BlockID) - if err != nil { - return sdkerrors.Wrap(err, "invalid block ID from header 1 in misbehaviour") - } - blockID2, err := tmtypes.BlockIDFromProto(&misbehaviour.Header2.SignedHeader.Commit.BlockID) - if err != nil { - return sdkerrors.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 { + if err := misbehaviour.Header1.ValidateCommit(); err != nil { return err } - if err := validCommit(misbehaviour.Header2.Header.ChainID, *blockID2, - misbehaviour.Header2.Commit, misbehaviour.Header2.ValidatorSet); err != nil { + if err := misbehaviour.Header2.ValidateCommit(); err != nil { return err } return nil } - -// validCommit checks if the given commit is a valid commit from the passed-in validatorset -func validCommit(chainID string, blockID tmtypes.BlockID, commit *tmproto.Commit, valSet *tmproto.ValidatorSet) (err error) { - tmCommit, err := tmtypes.CommitFromProto(commit) - if err != nil { - return sdkerrors.Wrap(err, "commit is not dymint commit type") - } - tmValset, err := tmtypes.ValidatorSetFromProto(valSet) - if err != nil { - return sdkerrors.Wrap(err, "validator set is not dymint validator set type") - } - - if err := tmValset.VerifyCommitLight(chainID, blockID, tmCommit.Height, tmCommit); err != nil { - return sdkerrors.Wrap(clienttypes.ErrInvalidMisbehaviour, "validator set did not commit to header") - } - - return nil -} diff --git a/modules/light-clients/01-dymint/types/update.go b/modules/light-clients/01-dymint/types/update.go index 1f1690e2b20..cd95f381378 100644 --- a/modules/light-clients/01-dymint/types/update.go +++ b/modules/light-clients/01-dymint/types/update.go @@ -186,6 +186,9 @@ func checkValidity( header *Header, currentTimestamp time.Time, ) error { + if err := header.ValidateCommit(); 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 { diff --git a/modules/light-clients/01-dymint/types/update_test.go b/modules/light-clients/01-dymint/types/update_test.go index 873e41b38a2..0d464b6eb10 100644 --- a/modules/light-clients/01-dymint/types/update_test.go +++ b/modules/light-clients/01-dymint/types/update_test.go @@ -262,6 +262,43 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { expFrozen: false, expPass: false, }, + { + name: "proposer is not in the validator set", + setup: func(suite *DymintTestSuite) { + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) + newValSet := suite.valSet + newValSet.Proposer = altVal + newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) + currentTime = suite.now + }, + expFrozen: false, + expPass: false, + }, + { + name: "wrong proposer address", + setup: func(suite *DymintTestSuite) { + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) + newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) + newHeader.SignedHeader.Commit.Signatures[0].ValidatorAddress = altVal.Address + currentTime = suite.now + }, + expFrozen: false, + expPass: false, + }, + { + name: "wrong proposer signature", + setup: func(suite *DymintTestSuite) { + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) + newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) + newHeader.SignedHeader.Commit.Signatures[0].Signature = []byte{123} + currentTime = suite.now + }, + expFrozen: false, + expPass: false, + }, } for i, tc := range testCases { From 70780bbdd981031c8f6cc089e577a7e335b3aa36 Mon Sep 17 00:00:00 2001 From: Lior Zilpa Date: Wed, 23 Nov 2022 17:39:33 +0200 Subject: [PATCH 117/140] ignore ConsensusState->NextValidatorsHash --- .../01-dymint/types/consensus_state.go | 9 ++------- .../01-dymint/types/consensus_state_test.go | 8 -------- modules/light-clients/01-dymint/types/header.go | 5 ++--- modules/light-clients/01-dymint/types/update.go | 15 ++++++--------- .../light-clients/01-dymint/types/update_test.go | 5 ++--- 5 files changed, 12 insertions(+), 30 deletions(-) diff --git a/modules/light-clients/01-dymint/types/consensus_state.go b/modules/light-clients/01-dymint/types/consensus_state.go index 87324cb0ecb..f938e3cda1b 100644 --- a/modules/light-clients/01-dymint/types/consensus_state.go +++ b/modules/light-clients/01-dymint/types/consensus_state.go @@ -5,7 +5,6 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" tmbytes "github.com/tendermint/tendermint/libs/bytes" - tmtypes "github.com/tendermint/tendermint/types" clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" @@ -20,9 +19,8 @@ func NewConsensusState( timestamp time.Time, root commitmenttypes.MerkleRoot, nextValsHash tmbytes.HexBytes, ) *ConsensusState { return &ConsensusState{ - Timestamp: timestamp, - Root: root, - NextValidatorsHash: nextValsHash, + Timestamp: timestamp, + Root: root, } } @@ -48,9 +46,6 @@ func (cs ConsensusState) ValidateBasic() error { if cs.Root.Empty() { return sdkerrors.Wrap(clienttypes.ErrInvalidConsensus, "root cannot be empty") } - if err := tmtypes.ValidateHash(cs.NextValidatorsHash); err != nil { - return sdkerrors.Wrap(err, "next validators hash is invalid") - } if cs.Timestamp.Unix() <= 0 { return sdkerrors.Wrap(clienttypes.ErrInvalidConsensus, "timestamp must be a positive Unix time") } diff --git a/modules/light-clients/01-dymint/types/consensus_state_test.go b/modules/light-clients/01-dymint/types/consensus_state_test.go index b9b020dd229..8e32c18998c 100644 --- a/modules/light-clients/01-dymint/types/consensus_state_test.go +++ b/modules/light-clients/01-dymint/types/consensus_state_test.go @@ -42,14 +42,6 @@ func (suite *DymintTestSuite) TestConsensusStateValidateBasic() { NextValidatorsHash: suite.valsHash, }, false}, - {"nextvalshash is invalid", - &types.ConsensusState{ - Timestamp: suite.now, - Root: commitmenttypes.NewMerkleRoot([]byte("app_hash")), - NextValidatorsHash: []byte("hi"), - }, - false}, - {"timestamp is zero", &types.ConsensusState{ Timestamp: time.Time{}, diff --git a/modules/light-clients/01-dymint/types/header.go b/modules/light-clients/01-dymint/types/header.go index 5baa3d65187..b1d119d9ca5 100644 --- a/modules/light-clients/01-dymint/types/header.go +++ b/modules/light-clients/01-dymint/types/header.go @@ -18,9 +18,8 @@ var _ exported.Header = &Header{} // 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, + Timestamp: h.GetTime(), + Root: commitmenttypes.NewMerkleRoot(h.Header.GetAppHash()), } } diff --git a/modules/light-clients/01-dymint/types/update.go b/modules/light-clients/01-dymint/types/update.go index cd95f381378..60f18f484c1 100644 --- a/modules/light-clients/01-dymint/types/update.go +++ b/modules/light-clients/01-dymint/types/update.go @@ -224,12 +224,11 @@ func checkValidity( } // Construct a trusted header using the fields in consensus state - // Only Height, Time, and NextValidatorsHash are necessary for verification + // Only Height and Time are necessary for verification trustedHeader := tmtypes.Header{ - ChainID: chainID, - Height: int64(header.TrustedHeight.RevisionHeight), - Time: consState.Timestamp, - NextValidatorsHash: consState.NextValidatorsHash, + ChainID: chainID, + Height: int64(header.TrustedHeight.RevisionHeight), + Time: consState.Timestamp, } signedHeader := tmtypes.SignedHeader{ Header: &trustedHeader, @@ -239,7 +238,6 @@ func checkValidity( // - 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 = _verifyNewHeaderAndVals( &signedHeader, tmSignedHeader, @@ -258,9 +256,8 @@ func update(ctx sdk.Context, clientStore sdk.KVStore, clientState *ClientState, clientState.LatestHeight = height } consensusState := &ConsensusState{ - Timestamp: header.GetTime(), - Root: commitmenttypes.NewMerkleRoot(header.Header.GetAppHash()), - NextValidatorsHash: header.Header.NextValidatorsHash, + Timestamp: header.GetTime(), + Root: commitmenttypes.NewMerkleRoot(header.Header.GetAppHash()), } // set metadata for this consensus state diff --git a/modules/light-clients/01-dymint/types/update_test.go b/modules/light-clients/01-dymint/types/update_test.go index 0d464b6eb10..37aac0ef770 100644 --- a/modules/light-clients/01-dymint/types/update_test.go +++ b/modules/light-clients/01-dymint/types/update_test.go @@ -327,9 +327,8 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { height := newHeader.GetHeight() expectedConsensus := &types.ConsensusState{ - Timestamp: newHeader.GetTime(), - Root: commitmenttypes.NewMerkleRoot(newHeader.Header.GetAppHash()), - NextValidatorsHash: newHeader.Header.NextValidatorsHash, + Timestamp: newHeader.GetTime(), + Root: commitmenttypes.NewMerkleRoot(newHeader.Header.GetAppHash()), } newClientState, consensusState, err := clientState.CheckHeaderAndUpdateState( From a67ae1b77d9bc6e155540f728b2d5495051c3f77 Mon Sep 17 00:00:00 2001 From: Lior Zilpa Date: Wed, 23 Nov 2022 17:40:12 +0200 Subject: [PATCH 118/140] clean TrustLevel and UnbondingPeriod leftovers --- .../light-clients/01-dymint/types/errors.go | 20 +++++++++---------- .../light-clients/01-dymint/types/fraction.go | 4 ---- .../light-clients/01-dymint/types/upgrade.go | 4 ++-- testing/chain_dymint.go | 5 ----- 4 files changed, 11 insertions(+), 22 deletions(-) diff --git a/modules/light-clients/01-dymint/types/errors.go b/modules/light-clients/01-dymint/types/errors.go index 6e9dd7dc6bd..e99dde63404 100644 --- a/modules/light-clients/01-dymint/types/errors.go +++ b/modules/light-clients/01-dymint/types/errors.go @@ -12,15 +12,13 @@ const ( var ( ErrInvalidChainID = sdkerrors.Register(SubModuleName, 2, "invalid chain-id") ErrInvalidTrustingPeriod = sdkerrors.Register(SubModuleName, 3, "invalid trusting period") - ErrInvalidUnbondingPeriod = sdkerrors.Register(SubModuleName, 4, "invalid unbonding period") - ErrInvalidHeaderHeight = sdkerrors.Register(SubModuleName, 5, "invalid header height") - ErrInvalidHeader = sdkerrors.Register(SubModuleName, 6, "invalid header") - ErrInvalidMaxClockDrift = sdkerrors.Register(SubModuleName, 7, "invalid max clock drift") - ErrProcessedTimeNotFound = sdkerrors.Register(SubModuleName, 8, "processed time not found") - ErrProcessedHeightNotFound = sdkerrors.Register(SubModuleName, 9, "processed height not found") - ErrDelayPeriodNotPassed = sdkerrors.Register(SubModuleName, 10, "packet-specified delay period has not been reached") - ErrTrustingPeriodExpired = sdkerrors.Register(SubModuleName, 11, "time since latest trusted state has passed the trusting period") - ErrUnbondingPeriodExpired = sdkerrors.Register(SubModuleName, 12, "time since latest trusted state has passed the unbonding period") - ErrInvalidProofSpecs = sdkerrors.Register(SubModuleName, 13, "invalid proof specs") - ErrInvalidValidatorSet = sdkerrors.Register(SubModuleName, 14, "invalid validator set") + ErrInvalidHeaderHeight = sdkerrors.Register(SubModuleName, 4, "invalid header height") + ErrInvalidHeader = sdkerrors.Register(SubModuleName, 5, "invalid header") + ErrInvalidMaxClockDrift = sdkerrors.Register(SubModuleName, 6, "invalid max clock drift") + ErrProcessedTimeNotFound = sdkerrors.Register(SubModuleName, 7, "processed time not found") + ErrProcessedHeightNotFound = sdkerrors.Register(SubModuleName, 8, "processed height not found") + ErrDelayPeriodNotPassed = sdkerrors.Register(SubModuleName, 9, "packet-specified delay period has not been reached") + ErrTrustingPeriodExpired = sdkerrors.Register(SubModuleName, 10, "time since latest trusted state has passed the trusting period") + ErrInvalidProofSpecs = sdkerrors.Register(SubModuleName, 12, "invalid proof specs") + ErrInvalidValidatorSet = sdkerrors.Register(SubModuleName, 13, "invalid validator set") ) diff --git a/modules/light-clients/01-dymint/types/fraction.go b/modules/light-clients/01-dymint/types/fraction.go index 1b4aa7606eb..8e1e2413afd 100644 --- a/modules/light-clients/01-dymint/types/fraction.go +++ b/modules/light-clients/01-dymint/types/fraction.go @@ -2,12 +2,8 @@ package types import ( tmmath "github.com/tendermint/tendermint/libs/math" - "github.com/tendermint/tendermint/light" ) -// DefaultTrustLevel is the dymint light client default trust level -var DefaultTrustLevel = NewFractionFromTm(light.DefaultTrustLevel) - // NewFractionFromTm returns a new Fraction instance from a tmmath.Fraction func NewFractionFromTm(f tmmath.Fraction) Fraction { return Fraction{ diff --git a/modules/light-clients/01-dymint/types/upgrade.go b/modules/light-clients/01-dymint/types/upgrade.go index 2202a6dbd5f..76153dfb708 100644 --- a/modules/light-clients/01-dymint/types/upgrade.go +++ b/modules/light-clients/01-dymint/types/upgrade.go @@ -22,7 +22,7 @@ import ( // 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 Dymint chain specified parameter in upgraded client such as ChainID, UnbondingPeriod, +// - any Dymint chain specified parameter in upgraded client such as ChainID, // and ProofSpecs do not match parameters set by committed client func (cs ClientState) VerifyUpgradeAndUpdateState( ctx sdk.Context, cdc codec.BinaryCodec, clientStore sdk.KVStore, @@ -110,7 +110,7 @@ func (cs ClientState) VerifyUpgradeAndUpdateState( // 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 + // The timestamp of the consensus state is the blocktime // 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 diff --git a/testing/chain_dymint.go b/testing/chain_dymint.go index 6378f2b3c17..ad4b0356672 100644 --- a/testing/chain_dymint.go +++ b/testing/chain_dymint.go @@ -19,11 +19,6 @@ import ( ibcdmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" ) -var ( - // Default params variables used to create a DM client - DefaultDymintTrustLevel ibcdmtypes.Fraction = ibcdmtypes.DefaultTrustLevel -) - type DymintConfig struct { TrustingPeriod time.Duration MaxClockDrift time.Duration From 4799d14db0718d39fed55866016e6bbe92696290 Mon Sep 17 00:00:00 2001 From: Lior Zilpa Date: Wed, 23 Nov 2022 17:54:12 +0200 Subject: [PATCH 119/140] ignore Header->TrustedValidators --- modules/light-clients/01-dymint/types/header.go | 4 ++-- modules/light-clients/01-dymint/types/misbehaviour.go | 6 ------ .../light-clients/01-dymint/types/misbehaviour_handle.go | 8 +------- 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/modules/light-clients/01-dymint/types/header.go b/modules/light-clients/01-dymint/types/header.go index b1d119d9ca5..e04f92797bf 100644 --- a/modules/light-clients/01-dymint/types/header.go +++ b/modules/light-clients/01-dymint/types/header.go @@ -45,7 +45,7 @@ func (h Header) GetTime() time.Time { // ValidateBasic calls the SignedHeader ValidateBasic function and checks // that validatorsets are not nil. -// NOTE: TrustedHeight and TrustedValidators may be empty when creating client +// NOTE: TrustedHeight may be empty when creating client // with MsgCreateClient func (h Header) ValidateBasic() error { if h.SignedHeader == nil { @@ -81,7 +81,7 @@ func (h Header) ValidateBasic() error { return nil } -// validCommit checks if the given commit is a valid commit from the passed-in validatorset +// ValidateCommit checks if the given commit is a valid commit from the passed-in validatorset func (h Header) ValidateCommit() (err error) { chainID := h.Header.ChainID blockID, err := tmtypes.BlockIDFromProto(&h.SignedHeader.Commit.BlockID) diff --git a/modules/light-clients/01-dymint/types/misbehaviour.go b/modules/light-clients/01-dymint/types/misbehaviour.go index e99bccaf99a..bf84ab590e9 100644 --- a/modules/light-clients/01-dymint/types/misbehaviour.go +++ b/modules/light-clients/01-dymint/types/misbehaviour.go @@ -59,12 +59,6 @@ func (misbehaviour Misbehaviour) ValidateBasic() error { if misbehaviour.Header2.TrustedHeight.RevisionHeight == 0 { return sdkerrors.Wrapf(ErrInvalidHeaderHeight, "misbehaviour Header2 cannot have zero revision height") } - if misbehaviour.Header1.TrustedValidators == nil { - return sdkerrors.Wrap(ErrInvalidValidatorSet, "trusted validator set in Header1 cannot be empty") - } - if misbehaviour.Header2.TrustedValidators == nil { - return sdkerrors.Wrap(ErrInvalidValidatorSet, "trusted validator set in Header2 cannot be empty") - } if misbehaviour.Header1.Header.ChainID != misbehaviour.Header2.Header.ChainID { return sdkerrors.Wrap(clienttypes.ErrInvalidMisbehaviour, "headers must have identical chainIDs") } diff --git a/modules/light-clients/01-dymint/types/misbehaviour_handle.go b/modules/light-clients/01-dymint/types/misbehaviour_handle.go index efdcaeb3e2b..95393762271 100644 --- a/modules/light-clients/01-dymint/types/misbehaviour_handle.go +++ b/modules/light-clients/01-dymint/types/misbehaviour_handle.go @@ -102,13 +102,7 @@ func (cs ClientState) CheckMisbehaviourAndUpdateState( func checkMisbehaviourHeader( clientState *ClientState, consState *ConsensusState, header *Header, currentTimestamp time.Time, ) error { - - _, err := tmtypes.ValidatorSetFromProto(header.TrustedValidators) - if err != nil { - return sdkerrors.Wrap(err, "trusted validator set is not dymint validator set type") - } - - _, err = tmtypes.CommitFromProto(header.Commit) + _, err := tmtypes.CommitFromProto(header.Commit) if err != nil { return sdkerrors.Wrap(err, "commit is not dymint commit type") } From 8c8d008e9cca890da2d73af4a4c1a0aeba07e25e Mon Sep 17 00:00:00 2001 From: liorzilp Date: Thu, 24 Nov 2022 10:56:07 +0200 Subject: [PATCH 120/140] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5a3cf3b7e7e..563e1f4a4c3 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,8 @@ The Inter-Blockchain Communication protocol (IBC) allows blockchains to talk to 3.1 [ICS 07 Tendermint](https://github.com/cosmos/ibc-go/tree/main/modules/light-clients/07-tendermint) 3.2 [ICS 06 Solo Machine](https://github.com/cosmos/ibc-go/tree/main/modules/light-clients/06-solomachine) + + 3.3 [DIP 01 Dymint](https://github.com/cosmos/ibc-go/tree/main/modules/light-clients/01-dymint) Note: The localhost client is currently non-functional. From 8dc49fc13f930a0c1ec24b6e4eea3997f60f3df6 Mon Sep 17 00:00:00 2001 From: liorzilp Date: Thu, 24 Nov 2022 10:58:28 +0200 Subject: [PATCH 121/140] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 563e1f4a4c3..5716e6bf059 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ The Inter-Blockchain Communication protocol (IBC) allows blockchains to talk to 3.2 [ICS 06 Solo Machine](https://github.com/cosmos/ibc-go/tree/main/modules/light-clients/06-solomachine) - 3.3 [DIP 01 Dymint](https://github.com/cosmos/ibc-go/tree/main/modules/light-clients/01-dymint) + 3.3 [DIP 01 Dymint](https://github.com/dymensionxyz/ibc-go/tree/main-dym/modules/light-clients/01-dymint) Note: The localhost client is currently non-functional. From e03dadc687a77186caea27f0298e2652b21b4c4a Mon Sep 17 00:00:00 2001 From: Lior Zilpa Date: Mon, 28 Nov 2022 15:44:40 +0200 Subject: [PATCH 122/140] add clientHooks --- modules/core/02-client/keeper/client.go | 24 +++++++++++++++++++ modules/core/02-client/keeper/keeper.go | 11 ++++++++- modules/core/exported/client.go | 32 +++++++++++++++++++++++++ modules/core/keeper/keeper.go | 4 ++-- modules/core/keeper/keeper_test.go | 1 + testing/simapp/app.go | 2 +- 6 files changed, 70 insertions(+), 4 deletions(-) diff --git a/modules/core/02-client/keeper/client.go b/modules/core/02-client/keeper/client.go index d8085ac719f..c1622237454 100644 --- a/modules/core/02-client/keeper/client.go +++ b/modules/core/02-client/keeper/client.go @@ -17,6 +17,12 @@ import ( func (k Keeper) CreateClient( ctx sdk.Context, clientState exported.ClientState, consensusState exported.ConsensusState, ) (string, error) { + if k.clientHooks != nil { + err := k.clientHooks.OnCreateClient(ctx, clientState, consensusState) + if err != nil { + return "", err + } + } params := k.GetParams(ctx) if !params.IsAllowedClient(clientState.ClientType()) { return "", sdkerrors.Wrapf( @@ -58,6 +64,12 @@ func (k Keeper) CreateClient( // UpdateClient updates the consensus state and the state root from a provided header. func (k Keeper) UpdateClient(ctx sdk.Context, clientID string, header exported.Header) error { + if k.clientHooks != nil { + err := k.clientHooks.OnUpdateClient(ctx, clientID, header) + if err != nil { + return err + } + } clientState, found := k.GetClientState(ctx, clientID) if !found { return sdkerrors.Wrapf(types.ErrClientNotFound, "cannot update client with ID %s", clientID) @@ -147,6 +159,12 @@ func (k Keeper) UpdateClient(ctx sdk.Context, clientID string, header exported.H // by the old client at the specified upgrade height func (k Keeper) UpgradeClient(ctx sdk.Context, clientID string, upgradedClient exported.ClientState, upgradedConsState exported.ConsensusState, proofUpgradeClient, proofUpgradeConsState []byte) error { + if k.clientHooks != nil { + err := k.clientHooks.OnUpgradeClient(ctx, clientID, upgradedClient, upgradedConsState, proofUpgradeClient, proofUpgradeConsState) + if err != nil { + return err + } + } clientState, found := k.GetClientState(ctx, clientID) if !found { return sdkerrors.Wrapf(types.ErrClientNotFound, "cannot update client with ID %s", clientID) @@ -189,6 +207,12 @@ func (k Keeper) UpgradeClient(ctx sdk.Context, clientID string, upgradedClient e // CheckMisbehaviourAndUpdateState checks for client misbehaviour and freezes the // client if so. func (k Keeper) CheckMisbehaviourAndUpdateState(ctx sdk.Context, misbehaviour exported.Misbehaviour) error { + if k.clientHooks != nil { + err := k.clientHooks.OnCheckMisbehaviourAndUpdateState(ctx, misbehaviour) + if err != nil { + return err + } + } clientState, found := k.GetClientState(ctx, misbehaviour.GetClientID()) if !found { return sdkerrors.Wrapf(types.ErrClientNotFound, "cannot check misbehaviour for client with ID %s", misbehaviour.GetClientID()) diff --git a/modules/core/02-client/keeper/keeper.go b/modules/core/02-client/keeper/keeper.go index cf558c174a7..809a5d012f8 100644 --- a/modules/core/02-client/keeper/keeper.go +++ b/modules/core/02-client/keeper/keeper.go @@ -26,10 +26,18 @@ type Keeper struct { stakingKeeper types.StakingKeeper upgradeKeeper types.UpgradeKeeper selfClient exported.SelfClient + clientHooks exported.ClientHooks } // NewKeeper creates a new NewKeeper instance -func NewKeeper(cdc codec.BinaryCodec, key sdk.StoreKey, paramSpace paramtypes.Subspace, sk types.StakingKeeper, uk types.UpgradeKeeper, selfClient exported.SelfClient) Keeper { +func NewKeeper( + cdc codec.BinaryCodec, + key sdk.StoreKey, + paramSpace paramtypes.Subspace, + sk types.StakingKeeper, + uk types.UpgradeKeeper, + selfClient exported.SelfClient, + clientHooks exported.ClientHooks) Keeper { // set KeyTable if it has not already been set if !paramSpace.HasKeyTable() { paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable()) @@ -42,6 +50,7 @@ func NewKeeper(cdc codec.BinaryCodec, key sdk.StoreKey, paramSpace paramtypes.Su stakingKeeper: sk, upgradeKeeper: uk, selfClient: selfClient, + clientHooks: clientHooks, } } diff --git a/modules/core/exported/client.go b/modules/core/exported/client.go index c6e7acdf822..5a7d833e784 100644 --- a/modules/core/exported/client.go +++ b/modules/core/exported/client.go @@ -258,6 +258,38 @@ type SelfClient interface { ) (ConsensusState, error) } +// ClientHooks defines an interface that implements callbacks +// for the client module methods as specified in ICS-02. +// First the callback is called and if no error, proceed to +// the method functionality +type ClientHooks interface { + OnCreateClient( + ctx sdk.Context, + clientState ClientState, + consensusState ConsensusState, + ) error + + OnUpdateClient( + ctx sdk.Context, + clientID string, + header Header, + ) error + + OnUpgradeClient( + ctx sdk.Context, + clientID string, + upgradedClient ClientState, + upgradedConsState ConsensusState, + proofUpgradeClient, + proofUpgradeConsState []byte, + ) error + + OnCheckMisbehaviourAndUpdateState( + ctx sdk.Context, + misbehaviour Misbehaviour, + ) error +} + // String returns the string representation of a client status. func (s Status) String() string { return string(s) diff --git a/modules/core/keeper/keeper.go b/modules/core/keeper/keeper.go index d550197fff0..0a0ea80998a 100644 --- a/modules/core/keeper/keeper.go +++ b/modules/core/keeper/keeper.go @@ -40,7 +40,7 @@ type Keeper struct { func NewKeeper( cdc codec.BinaryCodec, key sdk.StoreKey, paramSpace paramtypes.Subspace, stakingKeeper clienttypes.StakingKeeper, upgradeKeeper clienttypes.UpgradeKeeper, - scopedKeeper capabilitykeeper.ScopedKeeper, selfClient exported.SelfClient, + scopedKeeper capabilitykeeper.ScopedKeeper, selfClient exported.SelfClient, clientHooks exported.ClientHooks, ) *Keeper { // register paramSpace at top level keeper // set KeyTable if it has not already been set @@ -63,7 +63,7 @@ func NewKeeper( panic(fmt.Errorf("cannot initialize IBC keeper: empty scoped keeper")) } - clientKeeper := clientkeeper.NewKeeper(cdc, key, paramSpace, stakingKeeper, upgradeKeeper, selfClient) + clientKeeper := clientkeeper.NewKeeper(cdc, key, paramSpace, stakingKeeper, upgradeKeeper, selfClient, clientHooks) connectionKeeper := connectionkeeper.NewKeeper(cdc, key, paramSpace, clientKeeper) portKeeper := portkeeper.NewKeeper(scopedKeeper) channelKeeper := channelkeeper.NewKeeper(cdc, key, clientKeeper, connectionKeeper, portKeeper, scopedKeeper) diff --git a/modules/core/keeper/keeper_test.go b/modules/core/keeper/keeper_test.go index f0740740abd..47691a5ab6f 100644 --- a/modules/core/keeper/keeper_test.go +++ b/modules/core/keeper/keeper_test.go @@ -72,6 +72,7 @@ func (suite *KeeperTestSuite) TestNewKeeper() { upgradeKeeper, scopedKeeper, ibctmtypes.NewSelfClient(), + nil, ) } ) diff --git a/testing/simapp/app.go b/testing/simapp/app.go index 47b817323e0..38d340000ae 100644 --- a/testing/simapp/app.go +++ b/testing/simapp/app.go @@ -348,7 +348,7 @@ func NewSimAppWithConsensusType( } // Create IBC Keeper app.IBCKeeper = ibckeeper.NewKeeper( - appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, selfClient, + appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, selfClient, nil, ) app.AuthzKeeper = authzkeeper.NewKeeper(keys[authzkeeper.StoreKey], appCodec, app.BaseApp.MsgServiceRouter()) From 86c9ec9b6cf9ed4ba8e9acf792b04d38ba577034 Mon Sep 17 00:00:00 2001 From: Lior Zilpa Date: Tue, 29 Nov 2022 11:10:46 +0200 Subject: [PATCH 123/140] remove redundant test cases --- .../01-dymint/types/misbehaviour_test.go | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/modules/light-clients/01-dymint/types/misbehaviour_test.go b/modules/light-clients/01-dymint/types/misbehaviour_test.go index 6257e66f47d..d5b4c3f584b 100644 --- a/modules/light-clients/01-dymint/types/misbehaviour_test.go +++ b/modules/light-clients/01-dymint/types/misbehaviour_test.go @@ -134,26 +134,26 @@ func (suite *DymintTestSuite) TestMisbehaviourValidateBasic() { func(misbehaviour *types.Misbehaviour) error { return nil }, false, }, - { - "trusted valset is nil in Header1", - &types.Misbehaviour{ - Header1: dymintChain.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now.Add(time.Minute), suite.valSet, nil, signers), - Header2: suite.header, - ClientId: clientID, - }, - func(misbehaviour *types.Misbehaviour) error { return nil }, - false, - }, - { - "trusted valset is nil in Header2", - &types.Misbehaviour{ - Header1: suite.header, - Header2: dymintChain.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now.Add(time.Minute), suite.valSet, nil, signers), - ClientId: clientID, - }, - func(misbehaviour *types.Misbehaviour) error { return nil }, - false, - }, + // { + // "trusted valset is nil in Header1", + // &types.Misbehaviour{ + // Header1: dymintChain.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now.Add(time.Minute), suite.valSet, nil, signers), + // Header2: suite.header, + // ClientId: clientID, + // }, + // func(misbehaviour *types.Misbehaviour) error { return nil }, + // false, + // }, + // { + // "trusted valset is nil in Header2", + // &types.Misbehaviour{ + // Header1: suite.header, + // Header2: dymintChain.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now.Add(time.Minute), suite.valSet, nil, signers), + // ClientId: clientID, + // }, + // func(misbehaviour *types.Misbehaviour) error { return nil }, + // false, + // }, { "invalid client ID ", &types.Misbehaviour{ From 33f482480314f6a8e6334ba3cd3827b3a71152e3 Mon Sep 17 00:00:00 2001 From: Lior Zilpa Date: Wed, 30 Nov 2022 14:35:34 +0200 Subject: [PATCH 124/140] add GetChainID --- modules/core/02-client/legacy/v100/solomachine.go | 5 +++++ modules/core/exported/client.go | 3 +++ modules/light-clients/01-dymint/types/header.go | 5 +++++ modules/light-clients/01-dymint/types/misbehaviour.go | 6 ++++++ modules/light-clients/06-solomachine/types/client_state.go | 5 +++++ modules/light-clients/06-solomachine/types/header.go | 5 +++++ modules/light-clients/06-solomachine/types/misbehaviour.go | 5 +++++ modules/light-clients/07-tendermint/types/header.go | 5 +++++ modules/light-clients/07-tendermint/types/misbehaviour.go | 6 ++++++ 9 files changed, 45 insertions(+) diff --git a/modules/core/02-client/legacy/v100/solomachine.go b/modules/core/02-client/legacy/v100/solomachine.go index b9ae2b1005e..02846e9ba61 100644 --- a/modules/core/02-client/legacy/v100/solomachine.go +++ b/modules/core/02-client/legacy/v100/solomachine.go @@ -53,6 +53,11 @@ func (cs ClientState) ClientType() string { panic("legacy solo machine is deprecated!") } +// GetChainID panics! +func (cs ClientState) GetChainID() string { + panic("legacy solo machine is deprecated!") +} + // GetLatestHeight panics! func (cs ClientState) GetLatestHeight() exported.Height { panic("legacy solo machine is deprecated!") diff --git a/modules/core/exported/client.go b/modules/core/exported/client.go index 5a7d833e784..7b37d90c5c3 100644 --- a/modules/core/exported/client.go +++ b/modules/core/exported/client.go @@ -46,6 +46,7 @@ type ClientState interface { proto.Message ClientType() string + GetChainID() string GetLatestHeight() Height Validate() error @@ -204,6 +205,7 @@ type Misbehaviour interface { proto.Message ClientType() string + GetChainID() string GetClientID() string ValidateBasic() error } @@ -213,6 +215,7 @@ type Header interface { proto.Message ClientType() string + GetChainID() string GetHeight() Height ValidateBasic() error } diff --git a/modules/light-clients/01-dymint/types/header.go b/modules/light-clients/01-dymint/types/header.go index e04f92797bf..3a08307a5f5 100644 --- a/modules/light-clients/01-dymint/types/header.go +++ b/modules/light-clients/01-dymint/types/header.go @@ -28,6 +28,11 @@ func (h Header) ClientType() string { return exported.Dymint } +// GetChainID returns the chain-id +func (h Header) GetChainID() string { + return h.Header.ChainID +} + // GetHeight returns the current height. It returns 0 if the dymint // header is nil. // NOTE: the header.Header is checked to be non nil in ValidateBasic. diff --git a/modules/light-clients/01-dymint/types/misbehaviour.go b/modules/light-clients/01-dymint/types/misbehaviour.go index bf84ab590e9..78d4961bcd8 100644 --- a/modules/light-clients/01-dymint/types/misbehaviour.go +++ b/modules/light-clients/01-dymint/types/misbehaviour.go @@ -29,6 +29,12 @@ func (misbehaviour Misbehaviour) ClientType() string { return exported.Dymint } +// GetChainID returns the chain-id +func (misbehaviour Misbehaviour) GetChainID() string { + // assuming Header1.hainID and Header2.hainID are same as checked in ValidateBasic + return misbehaviour.Header1.GetChainID() +} + // GetClientID returns the ID of the client that committed a misbehaviour. func (misbehaviour Misbehaviour) GetClientID() string { return misbehaviour.ClientId diff --git a/modules/light-clients/06-solomachine/types/client_state.go b/modules/light-clients/06-solomachine/types/client_state.go index d92f69b98e4..39813840fb4 100644 --- a/modules/light-clients/06-solomachine/types/client_state.go +++ b/modules/light-clients/06-solomachine/types/client_state.go @@ -32,6 +32,11 @@ func (cs ClientState) ClientType() string { return exported.Solomachine } +// GetChainID returns the chain-id +func (cs ClientState) GetChainID() string { + return "" +} + // GetLatestHeight returns the latest sequence number. // Return exported.Height to satisfy ClientState interface // Revision number is always 0 for a solo-machine. diff --git a/modules/light-clients/06-solomachine/types/header.go b/modules/light-clients/06-solomachine/types/header.go index 7bcfb9937c5..3df9ca16e3f 100644 --- a/modules/light-clients/06-solomachine/types/header.go +++ b/modules/light-clients/06-solomachine/types/header.go @@ -17,6 +17,11 @@ func (Header) ClientType() string { return exported.Solomachine } +// GetChainID returns the chain-id +func (h Header) GetChainID() string { + return "" +} + // GetHeight returns the current sequence number as the height. // Return clientexported.Height to satisfy interface // Revision number is always 0 for a solo-machine diff --git a/modules/light-clients/06-solomachine/types/misbehaviour.go b/modules/light-clients/06-solomachine/types/misbehaviour.go index f5df3e1bad9..7ca047f3b07 100644 --- a/modules/light-clients/06-solomachine/types/misbehaviour.go +++ b/modules/light-clients/06-solomachine/types/misbehaviour.go @@ -17,6 +17,11 @@ func (misbehaviour Misbehaviour) ClientType() string { return exported.Solomachine } +// GetChainID returns the chain-id +func (misbehaviour Misbehaviour) GetChainID() string { + return "" +} + // GetClientID returns the ID of the client that committed a misbehaviour. func (misbehaviour Misbehaviour) GetClientID() string { return misbehaviour.ClientId diff --git a/modules/light-clients/07-tendermint/types/header.go b/modules/light-clients/07-tendermint/types/header.go index 58e7d671e4a..5329b8ee32a 100644 --- a/modules/light-clients/07-tendermint/types/header.go +++ b/modules/light-clients/07-tendermint/types/header.go @@ -28,6 +28,11 @@ func (h Header) ClientType() string { return exported.Tendermint } +// GetChainID returns the chain-id +func (h Header) GetChainID() string { + return h.Header.ChainID +} + // 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. diff --git a/modules/light-clients/07-tendermint/types/misbehaviour.go b/modules/light-clients/07-tendermint/types/misbehaviour.go index 28ea7aa3666..f878d953ee0 100644 --- a/modules/light-clients/07-tendermint/types/misbehaviour.go +++ b/modules/light-clients/07-tendermint/types/misbehaviour.go @@ -31,6 +31,12 @@ func (misbehaviour Misbehaviour) ClientType() string { return exported.Tendermint } +// GetChainID returns the chain-id +func (misbehaviour Misbehaviour) GetChainID() string { + // assuming Header1.hainID and Header2.hainID are same as checked in ValidateBasic + return misbehaviour.Header1.GetChainID() +} + // GetClientID returns the ID of the client that committed a misbehaviour. func (misbehaviour Misbehaviour) GetClientID() string { return misbehaviour.ClientId From ba5bed4a94cf837448f4c7604f97e3f38be4cc51 Mon Sep 17 00:00:00 2001 From: Lior Zilpa Date: Tue, 6 Dec 2022 15:00:31 +0200 Subject: [PATCH 125/140] add GetLastHeader --- testing/chain.go | 1 + testing/chain_dymint.go | 9 ++++++--- testing/chain_tendermint.go | 9 ++++++--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/testing/chain.go b/testing/chain.go index 291bdb0f507..cb088d653e9 100644 --- a/testing/chain.go +++ b/testing/chain.go @@ -45,6 +45,7 @@ type TestChainClientI interface { GetConsensusState() exported.ConsensusState NewConfig() ClientConfig GetSelfClientType() string + GetLastHeader() interface{} } func NewTestChainClient(chain *TestChain, chainConsensusType string) TestChainClientI { diff --git a/testing/chain_dymint.go b/testing/chain_dymint.go index ad4b0356672..716e4fdaa0a 100644 --- a/testing/chain_dymint.go +++ b/testing/chain_dymint.go @@ -109,8 +109,7 @@ func (chain *TestChainDymint) NextBlock() { // ConstructUpdateDMClientHeader will construct a valid 01-dymint Header to update the // light client on the source chain. func ConstructUpdateDMClientHeaderWithTrustedHeight(counterparty *TestChain, clientID string, trustedHeight clienttypes.Height) (*ibcdmtypes.Header, error) { - counterpartyTestChainDymint := counterparty.TestChainClient.(*TestChainDymint) - header := counterpartyTestChainDymint.LastHeader + header := counterparty.TestChainClient.GetLastHeader().(*ibcdmtypes.Header) var ( tmTrustedVals *tmtypes.ValidatorSet @@ -119,7 +118,7 @@ func ConstructUpdateDMClientHeaderWithTrustedHeight(counterparty *TestChain, cli // Once we get TrustedHeight from client, we must query the validators from the counterparty chain // If the LatestHeight == LastHeader.Height, then TrustedValidators are current validators // If LatestHeight < LastHeader.Height, we can query the historical validator set from HistoricalInfo - if trustedHeight == counterpartyTestChainDymint.LastHeader.GetHeight() { + if trustedHeight == header.GetHeight() { tmTrustedVals = counterparty.Vals } else { // NOTE: We need to get validators from counterparty at height: trustedHeight+1 @@ -237,3 +236,7 @@ func (chain *TestChainDymint) ClientConfigToState(clientConfig ClientConfig) exp func (chain *TestChainDymint) GetConsensusState() exported.ConsensusState { return chain.LastHeader.ConsensusState() } + +func (chain *TestChainDymint) GetLastHeader() interface{} { + return chain.LastHeader +} diff --git a/testing/chain_tendermint.go b/testing/chain_tendermint.go index 24903ff7eeb..5cbc6a72adf 100644 --- a/testing/chain_tendermint.go +++ b/testing/chain_tendermint.go @@ -127,8 +127,7 @@ func (chain *TestChainTendermint) ConstructUpdateTMClientHeader(counterparty *Te // ConstructUpdateTMClientHeader will construct a valid 07-tendermint Header to update the // light client on the source chain. func ConstructUpdateTMClientHeaderWithTrustedHeight(counterparty *TestChain, clientID string, trustedHeight clienttypes.Height) (*ibctmtypes.Header, error) { - counterpartyTestChainTendermint := counterparty.TestChainClient.(*TestChainTendermint) - header := counterpartyTestChainTendermint.LastHeader + header := counterparty.TestChainClient.GetLastHeader().(*ibctmtypes.Header) // Relayer must query for LatestHeight on client to get TrustedHeight if the trusted height is not set require.False(counterparty.T, trustedHeight.IsZero()) @@ -139,7 +138,7 @@ func ConstructUpdateTMClientHeaderWithTrustedHeight(counterparty *TestChain, cli // Once we get TrustedHeight from client, we must query the validators from the counterparty chain // If the LatestHeight == LastHeader.Height, then TrustedValidators are current validators // If LatestHeight < LastHeader.Height, we can query the historical validator set from HistoricalInfo - if trustedHeight == counterpartyTestChainTendermint.LastHeader.GetHeight() { + if trustedHeight == header.GetHeight() { tmTrustedVals = counterparty.Vals } else { // NOTE: We need to get validators from counterparty at height: trustedHeight+1 @@ -288,3 +287,7 @@ func (chain *TestChainTendermint) ClientConfigToState(clientConfig ClientConfig) func (chain *TestChainTendermint) GetConsensusState() exported.ConsensusState { return chain.LastHeader.ConsensusState() } + +func (chain *TestChainTendermint) GetLastHeader() interface{} { + return chain.LastHeader +} From 1e5a27e827dff898e097a4a765755a100d9b9da7 Mon Sep 17 00:00:00 2001 From: Lior Zilpa Date: Tue, 6 Dec 2022 15:01:22 +0200 Subject: [PATCH 126/140] fix missing EndBlock() / remove BeginBlock() --- testing/chain.go | 2 ++ testing/coordinator.go | 6 +++++- testing/simapp/test_helpers.go | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/testing/chain.go b/testing/chain.go index cb088d653e9..4446b449f49 100644 --- a/testing/chain.go +++ b/testing/chain.go @@ -389,6 +389,7 @@ func (chain *TestChain) CreatePortCapability(scopedKeeper capabilitykeeper.Scope require.NoError(chain.T, err) } + chain.App.EndBlock(abci.RequestEndBlock{Height: chain.App.LastBlockHeight()}) chain.App.Commit() chain.NextBlock() @@ -417,6 +418,7 @@ func (chain *TestChain) CreateChannelCapability(scopedKeeper capabilitykeeper.Sc require.NoError(chain.T, err) } + chain.App.EndBlock(abci.RequestEndBlock{Height: chain.App.LastBlockHeight()}) chain.App.Commit() chain.NextBlock() diff --git a/testing/coordinator.go b/testing/coordinator.go index fa3b9bbc22d..d9c4cf8f019 100644 --- a/testing/coordinator.go +++ b/testing/coordinator.go @@ -9,6 +9,7 @@ import ( clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" "github.com/cosmos/ibc-go/v3/modules/core/exported" "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" ) var ( @@ -212,6 +213,7 @@ func GetChainID(index int) string { // CONTRACT: the passed in list of indexes must not contain duplicates func (coord *Coordinator) CommitBlock(chains ...*TestChain) { for _, chain := range chains { + chain.App.EndBlock(abci.RequestEndBlock{Height: chain.App.LastBlockHeight()}) chain.App.Commit() chain.NextBlock() } @@ -221,9 +223,11 @@ func (coord *Coordinator) CommitBlock(chains ...*TestChain) { // 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.TestChainClient.BeginBlock() + // chain.TestChainClient.BeginBlock() + chain.App.EndBlock(abci.RequestEndBlock{Height: chain.App.LastBlockHeight()}) chain.App.Commit() chain.NextBlock() + // IncrementTime calls to BeginBlock() coord.IncrementTime() } } diff --git a/testing/simapp/test_helpers.go b/testing/simapp/test_helpers.go index 11dcd85b515..b1c67d5fcd7 100644 --- a/testing/simapp/test_helpers.go +++ b/testing/simapp/test_helpers.go @@ -108,6 +108,7 @@ func SetupWithGenesisAccounts(genAccs []authtypes.GenesisAccount, balances ...ba }, ) + app.EndBlock(abci.RequestEndBlock{Height: app.LastBlockHeight()}) app.Commit() app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: app.LastBlockHeight() + 1}}) From 06ff366809a8b5141297cb334397b334ae02671e Mon Sep 17 00:00:00 2001 From: Itzhak Bokris Date: Wed, 21 Dec 2022 11:32:07 +0200 Subject: [PATCH 127/140] support dymint signatures verification + add dymint to the default-allowrd-clients list (#23) --- modules/core/02-client/types/params.go | 4 ++-- modules/light-clients/01-dymint/types/header.go | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/core/02-client/types/params.go b/modules/core/02-client/types/params.go index 884fec2e5f4..f203cd21fd7 100644 --- a/modules/core/02-client/types/params.go +++ b/modules/core/02-client/types/params.go @@ -10,8 +10,8 @@ import ( ) var ( - // DefaultAllowedClients are "06-solomachine" and "07-tendermint" - DefaultAllowedClients = []string{exported.Solomachine, exported.Tendermint} + // DefaultAllowedClients are "01-dymint", "06-solomachine" and "07-tendermint" + DefaultAllowedClients = []string{exported.Dymint, exported.Solomachine, exported.Tendermint} // KeyAllowedClients is store's key for AllowedClients Params KeyAllowedClients = []byte("AllowedClients") diff --git a/modules/light-clients/01-dymint/types/header.go b/modules/light-clients/01-dymint/types/header.go index 3a08307a5f5..32d4077755f 100644 --- a/modules/light-clients/01-dymint/types/header.go +++ b/modules/light-clients/01-dymint/types/header.go @@ -88,7 +88,6 @@ func (h Header) ValidateBasic() error { // ValidateCommit checks if the given commit is a valid commit from the passed-in validatorset func (h Header) ValidateCommit() (err error) { - chainID := h.Header.ChainID blockID, err := tmtypes.BlockIDFromProto(&h.SignedHeader.Commit.BlockID) if err != nil { return sdkerrors.Wrap(err, "invalid block ID from header SignedHeader.Commit") @@ -116,15 +115,16 @@ func (h Header) ValidateCommit() (err error) { return sdkerrors.Wrap(clienttypes.ErrInvalidHeader, "validator set did not commit to header") } // Validate signature. - voteSignBytes := tmCommit.VoteSignBytes(chainID, valIdx) if !bytes.Equal(commitSig.ValidatorAddress, h.Header.ProposerAddress) { return fmt.Errorf("wrong proposer address in commit, got %X) but expected %X", valIdx, h.Header.ProposerAddress) } - if !val.PubKey.VerifySignature(voteSignBytes, commitSig.Signature) { + headerBytes, err := h.SignedHeader.Header.Marshal() + if err != nil { + return err + } + if !val.PubKey.VerifySignature(headerBytes, commitSig.Signature) { return fmt.Errorf("wrong signature (#%d): %X", valIdx, commitSig.Signature) } - } else { - return sdkerrors.Wrap(clienttypes.ErrInvalidHeader, "validator set did not commit to header") } return nil From bcb8546c4c87ea4639cb2167cb1b978fcab4326c Mon Sep 17 00:00:00 2001 From: liorzilp Date: Wed, 4 Jan 2023 11:23:12 +0200 Subject: [PATCH 128/140] Liorzilp/25 upgrade v3.4.0 (#26) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * override default docsBranch (#1355) (#1390) ## Description closes: #1354 --- 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. - [x] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [x] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [x] Re-reviewed `Files changed` in the Github PR explorer - [x] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit 2709c241b0412ee2e6887bc818f24418cdef469f) Co-authored-by: Carlos Rodriguez * build(deps): bump github.com/cosmos/cosmos-sdk from 0.45.3 to 0.45.4 (#1300) (#1365) * build(deps): bump github.com/cosmos/cosmos-sdk from 0.45.3 to 0.45.4 Bumps [github.com/cosmos/cosmos-sdk](https://github.com/cosmos/cosmos-sdk) from 0.45.3 to 0.45.4. - [Release notes](https://github.com/cosmos/cosmos-sdk/releases) - [Changelog](https://github.com/cosmos/cosmos-sdk/blob/v0.45.4/CHANGELOG.md) - [Commits](https://github.com/cosmos/cosmos-sdk/compare/v0.45.3...v0.45.4) --- updated-dependencies: - dependency-name: github.com/cosmos/cosmos-sdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * update changelog Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> Co-authored-by: Carlos Rodriguez (cherry picked from commit bd086506f9256c4d5776bc38d2e930b523eb5125) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * add empty keepers checking in ibc NewKeeper (backport #1284) (#1382) * add empty keepers checking in ibc NewKeeper (#1284) * add empty keepers checking in ibc NewKeeper * check for empty exported keepers instead of empty sdk-defined keeper structs * Update modules/core/keeper/keeper.go Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> * remove func checkEmptyKeepers(), check empty keepers directly within func NewKeeper() * modules/core/keeper KeeperTestSuite -> MsgServerTestSuite; creat new modules/core/keeper KeeperTestSuite for testing ibckeeper.NewKeeper() * update CHANGELOG.md * DummyStakingKeeper -> MockStakingKeeper * refactor modules/core/keeper test * Update modules/core/keeper/keeper_test.go Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> * Update modules/core/keeper/keeper.go Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> (cherry picked from commit f2577f97c5341c09232a0c56d8a1a62ad6ea0b8a) # Conflicts: # CHANGELOG.md * fix conflict * fix conflict Co-authored-by: khanh <50263489+catShaark@users.noreply.github.com> Co-authored-by: crodriguezvega * fix: prefix ResponseResultType enum for proto linting (#1143) (#1393) (cherry picked from commit 5cf580cbd9e87a6385928b7440b26f7e92b37c43) Co-authored-by: Damian Nolan * chore : add selected channel version to MsgChanOpenInitResponse and MsgChanOpenTryResponse (backport #1279) (#1374) * chore : add selected channel version to MsgChanOpenInitResponse and MsgChanOpenTryResponse (#1279) ## Description - Add a version field to MsgChannelOpenInitResponse and MsgChannelOpenTryResponse in proto and gen proto - Set the selected channel version in the [MsgChannelOpenInitResponse](https://github.com/notional-labs/ibc-go/blob/ed7a082565fadb9ce27067fa1efb56c23fafc8ef/modules/core/keeper/msg_server.go#L197) and [MsgChannelOpenTryResponse](https://github.com/notional-labs/ibc-go/blob/ed7a082565fadb9ce27067fa1efb56c23fafc8ef/modules/core/keeper/msg_server.go#L237) --- 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. - [ ] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [ ] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes closes: #1204 (cherry picked from commit a1878038ca40d117defd3688ad1494e9872b2ffb) # Conflicts: # CHANGELOG.md # modules/core/04-channel/types/tx.pb.go * fix conflict * regenerate proto file * go mod tidy Co-authored-by: vuong <56973102+nguyenvuong1122000@users.noreply.github.com> Co-authored-by: Carlos Rodriguez * feat: Add sender to fungible_token_packet events (backport #1414) (#1427) * Add Sender to funginble_token_packet events (cherry picked from commit f05a7cf8840ead622e7b5628466e1f9a311a717d) * chore: Updated CHANGELOG.md (cherry picked from commit c809c518423de21c576b8a80fec7fb5f5e07fca8) # Conflicts: # CHANGELOG.md * Update CHANGELOG.md (cherry picked from commit d8be3d0ce674c004d7543413cbeba3455d7028e2) # Conflicts: # CHANGELOG.md * Update CHANGELOG.md Co-authored-by: Damian Nolan (cherry picked from commit 46d73a09352d7db7b01d006f64627d519919f44e) # Conflicts: # CHANGELOG.md * fix conflicts Co-authored-by: chatton Co-authored-by: Cian Hatton Co-authored-by: Carlos Rodriguez * add swagger for interchain accounts (#1402) (#1411) ## 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. - [x] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [x] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit bf444a6f68c0c5ddfb446360e536a71ccc3ff83a) Co-authored-by: Carlos Rodriguez * add actual parameter example to denom-trace and denom-hash CLI queries (backport #1442) (#1463) * add actual parameter example to denom-trace and denom-hash CLI queries (#1442) (cherry picked from commit 8062d014956682b153f82e247d943310f5ff62a9) # Conflicts: # modules/apps/transfer/client/cli/query.go * fix conflict Co-authored-by: Carlos Rodriguez * Emit channel close event on ordered channel close (backport #1464) (#1476) * Emit channel close event on ordered channel close (#1464) (cherry picked from commit 9ed5ca4121d52c8a99076360acf62e52cf7887ea) # Conflicts: # CHANGELOG.md * fix conflict Co-authored-by: Cian Hatton Co-authored-by: Carlos Rodriguez * backport #1416 (#1496) Co-authored-by: Cian Hatton * chore: improve DenomTrace grpc (backport #1342) (#1504) * chore: improve DenomTrace grpc (#1342) * change DenomTrace grpc * update CHANGELOG.md * minor * minor * minor * minor * minor * minor * Update modules/apps/transfer/keeper/grpc_query_test.go Co-authored-by: Carlos Rodriguez * Update modules/apps/transfer/keeper/grpc_query_test.go Co-authored-by: Carlos Rodriguez * Update CHANGELOG.md Co-authored-by: Damian Nolan * use TrimPrefix() in DenomTrace() * update migration doc Co-authored-by: Carlos Rodriguez Co-authored-by: Damian Nolan (cherry picked from commit 23e7e7dcfbe6303e83fe1f6e3d984ade97fcbbd9) # Conflicts: # CHANGELOG.md # docs/ibc/proto-docs.md # docs/migrations/v3-to-v4.md # modules/apps/transfer/client/cli/query.go * fix conflicts Co-authored-by: khanh <50263489+catShaark@users.noreply.github.com> Co-authored-by: crodriguezvega * Emit an event to indicate a successful acknowledgement in the ICA module (backport #1466) (#1509) * Emit an event to indicate a successful acknowledgement in the ICA module (#1466) (cherry picked from commit b2ca1932849cc3f05fe9e6ec30049aa4d08e6fc6) # Conflicts: # modules/apps/27-interchain-accounts/host/keeper/events.go * fix conflicts Co-authored-by: Cian Hatton Co-authored-by: crodriguezvega * chore: Add consensus state heights query (backport #1336) (#1507) * chore: Add consensus state heights query (#1336) * add ConsensusStateHeights query * add cli for ConsensusStateHeights Query * update CHANGELOG.md * Update modules/core/02-client/keeper/grpc_query.go Co-authored-by: Damian Nolan * Update modules/core/02-client/client/cli/query.go Co-authored-by: Damian Nolan * Update modules/core/02-client/client/cli/query.go Co-authored-by: Damian Nolan * Update modules/core/02-client/client/cli/query.go Co-authored-by: Damian Nolan * update consensus height query * very minor changes in modules/core/02-client grpc_query_test * Update modules/core/02-client/keeper/grpc_query_test.go Co-authored-by: Sean King * Update modules/core/02-client/keeper/grpc_query_test.go Co-authored-by: Sean King * Update modules/core/02-client/keeper/grpc_query_test.go Co-authored-by: Sean King * Update modules/core/02-client/keeper/grpc_query_test.go Co-authored-by: Sean King * Update modules/core/02-client/client/cli/query.go Co-authored-by: Sean King * Update modules/core/02-client/keeper/grpc_query_test.go Co-authored-by: Carlos Rodriguez * Update CHANGELOG.md Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> * update swagger.yaml; update 02-client grpc_query_test * Update modules/core/02-client/keeper/grpc_query_test.go Co-authored-by: Damian Nolan * nit Co-authored-by: Damian Nolan Co-authored-by: Sean King Co-authored-by: Carlos Rodriguez Co-authored-by: vuong <56973102+vuong177@users.noreply.github.com> Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> (cherry picked from commit 042d8187d751bdacdecbb1e730f3dc09da24caed) # Conflicts: # CHANGELOG.md # docs/client/swagger-ui/swagger.yaml * fix conflicts and change gRPC web route to consensus_state/{client_id}/heights Co-authored-by: khanh <50263489+catShaark@users.noreply.github.com> Co-authored-by: crodriguezvega * fix: deprecate AllowUpdateAfter...check (backport #1511) (#1521) * fix: deprecate AllowUpdateAfter...check (#1511) * fix: deprecate AllowUpdateAfter...check * update IsMatchingClientState * rm unnecessary fields in testing (cherry picked from commit 5e5e2cd2b904738266b06b3fc63a744fe6c7eeab) # Conflicts: # CHANGELOG.md * fix conflicts * Update CHANGELOG.md * Update adr-026-ibc-client-recovery-mechanisms.md Co-authored-by: Charly Co-authored-by: Carlos Rodriguez Co-authored-by: Carlos Rodriguez * fix to correctly parse denoms with slashes in the base denom (backport #1451) (#1536) * fix to correctly parse denoms with slashes in the base denom (#1451) * fix to correctly parse denoms with slashes in the base denom * some logic refinement * review comments * add changelog entry an other review comments * review comment * Add slash migration guide (#1518) * add migration guide * Update docs/migrations/support-slashed-denoms.md Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> * clarify upgrade name * remove unnecessary store loader * review comment, update migration code Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> Co-authored-by: Carlos Rodriguez * rename migration file Co-authored-by: Carlos Rodriguez Co-authored-by: Aditya Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> (cherry picked from commit 3a235af89e32322dee29cd9b06bbbe420dc8abab) # Conflicts: # CHANGELOG.md * fix conflict Co-authored-by: Carlos Rodriguez * Update CHANGELOG.md * Update CHANGELOG.md * Update CHANGELOG.md * Update CHANGELOG.md * feat: added check for wildcard * to allow all message types (#1512) (#1537) * added check for wildcard * to allow all message types * update docs * nit (cherry picked from commit 08d91ada9c3a1cb3394c047b2364481404ec6742) Co-authored-by: Charly * Update versions * Update config.js * Delete v3-to-v4.md * build(deps): bump github.com/cosmos/cosmos-sdk from 0.45.4 to 0.45.5 (backport #1525) (#1547) * build(deps): bump github.com/cosmos/cosmos-sdk from 0.45.4 to 0.45.5 (#1525) * build(deps): bump github.com/cosmos/cosmos-sdk from 0.45.4 to 0.45.5 Bumps [github.com/cosmos/cosmos-sdk](https://github.com/cosmos/cosmos-sdk) from 0.45.4 to 0.45.5. - [Release notes](https://github.com/cosmos/cosmos-sdk/releases) - [Changelog](https://github.com/cosmos/cosmos-sdk/blob/v0.45.5/CHANGELOG.md) - [Commits](https://github.com/cosmos/cosmos-sdk/compare/v0.45.4...v0.45.5) --- updated-dependencies: - dependency-name: github.com/cosmos/cosmos-sdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * updating changelog to reflect SDK v0.45.5 upgrade Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Damian Nolan Co-authored-by: Carlos Rodriguez (cherry picked from commit 681a558e3d2da314313c0edde93b29d59b3b18be) # Conflicts: # CHANGELOG.md * fix conflicts Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Carlos Rodriguez * add migration for base denoms with slashes to docs site (#1544) (#1557) ## 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. - [ ] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [ ] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit 6c034bc085e1701a7cbaa26476eb33e958b3a57c) Co-authored-by: Carlos Rodriguez * docs: add upgrade client proposal event (#1596) (#1635) (cherry picked from commit 41282c772bd8494f9b48d9ebdb40be7d5f7abd18) Co-authored-by: Carlos Rodriguez * feat: emitting an event when handling a client upgrade proposal (backport #1570) (#1594) * feat: emitting an event when handling a client upgrade proposal (#1570) * feat: emitting an event when handling a client upgrade proposal * refactor: only emit event if err is nil * refactor: idiotmatic go: (cherry picked from commit 8422d0c4c35ef970539466c5bdec1cd27369bab3) # Conflicts: # CHANGELOG.md * Update CHANGELOG.md Co-authored-by: Sean King * deps: bumping go version 1.18 (backport #1627) (#1656) * deps: bumping go version 1.18 (#1627) * bumping go version 1.18 * updating broken workflow setup (cherry picked from commit 7d971ecc79a347bd8ca278274c0d98ecadc94d64) # Conflicts: # .github/workflows/test.yml # Dockerfile # go.sum * fixing merge conflicts Co-authored-by: Damian Nolan * build(deps): bump github.com/cosmos/cosmos-sdk from 0.45.5 to 0.45.6 (backport #1615) (#1658) * build(deps): bump github.com/cosmos/cosmos-sdk from 0.45.5 to 0.45.6 (#1615) * build(deps): bump github.com/cosmos/cosmos-sdk from 0.45.5 to 0.45.6 Bumps [github.com/cosmos/cosmos-sdk](https://github.com/cosmos/cosmos-sdk) from 0.45.5 to 0.45.6. - [Release notes](https://github.com/cosmos/cosmos-sdk/releases) - [Changelog](https://github.com/cosmos/cosmos-sdk/blob/main/CHANGELOG.md) - [Commits](https://github.com/cosmos/cosmos-sdk/compare/v0.45.5...v0.45.6) --- updated-dependencies: - dependency-name: github.com/cosmos/cosmos-sdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Update CHANGELOG.md * copying part of codeql workflow to try to make it pass Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Carlos Rodriguez Co-authored-by: Carlos Rodriguez (cherry picked from commit e04964912c266bab923253c48d72cc8ec8b38f5e) # Conflicts: # .github/workflows/codeql-analysis.yml # CHANGELOG.md * fixing conflicts in changelog.md Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Damian Nolan * Update CHANGELOG.md * fix typo * chore: denom traces migration handler (backport #1680) (#1754) * chore: denom traces migration handler (#1680) * update code & test * register migrator service (cherry picked from commit be5ccf3a8007c69502fd34ba281112720a2f8578) # Conflicts: # CHANGELOG.md # docs/migrations/support-denoms-with-slashes.md # docs/migrations/v3-to-v4.md # modules/apps/transfer/types/trace.go * fix conflicts * fix go version package * put back entry that got removed by mistake Co-authored-by: Charly Co-authored-by: crodriguezvega * feat: allow governance to update the TrustingPeriod of the 07-tendermint light client (backport #1713) (#1761) * feat: allow governance to update the TrustingPeriod of the 07-tendermint light client (#1713) * initial commit * format imports * update docs * update CHANGELOG * update upgrade dev docs * update re: pr comments (cherry picked from commit c12789d882a3a8ea4ca078ca9783a677e0a56ea4) * position correctly changelog entry Co-authored-by: Charly Co-authored-by: Carlos Rodriguez * fix broken link (#1776) (#1808) (cherry picked from commit ef7a5c72aa93bfa7290505788fbda455cd07b318) Co-authored-by: Carlos Rodriguez * fix: add cosmos_proto.implements_interface (backport #1740) (#1817) * fix: add cosmos_proto.implements_interface (#1740) * fix: add cosmos_proto.implements_interface * changelog * Update CHANGELOG.md Co-authored-by: Carlos Rodriguez * run `make proto-all` * run `go mod tidy` Co-authored-by: Carlos Rodriguez Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> (cherry picked from commit 40d0ff7a2447b65f9401bb22547b2d397ce4d71e) # Conflicts: # CHANGELOG.md # modules/core/02-client/types/client.pb.go * fix conflict Co-authored-by: Dan Lynch Co-authored-by: crodriguezvega * fix: ics27 check packet data length explicitly over nil check (#1882) (#1897) * using len check in favour of nil check for interchain account packet data * adding changelog * updating changelog (cherry picked from commit 73fdde9968200edcf00fe72d419e003f262b3275) Co-authored-by: Damian Nolan * chore: adding dockerfile and release task to release/3.2.x (#1932) * fix: prevent blocked addresses from sending ICS 20 transfers (backport #1907) (#1945) * fix: prevent blocked addresses from sending ICS 20 transfers (#1907) * fix bug, add test Ensures that a sender account isn't a blocked address Added test cases for MsgTransfer handling * update documentation * move blocked address check to SendTransfer * add changelog entry (cherry picked from commit f891c2949e3c3d92846b04b64ce23551b6528897) # Conflicts: # modules/apps/transfer/keeper/relay_test.go * fix conflicts Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> * Fixing github action workflows (backport #1428) (#1939) * chore: backport #1905 (#1978) Co-authored-by: Devashish Dixit * fix: "acknowledgement written" logs unsupported type (backport #1919) (#1960) * fix: "acknowledgement written" logs unsupported type (#1919) * fix: "acknowledgement written" logs unsupported type * Updating CHANGELOG.md Co-authored-by: Carlos Rodriguez Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> (cherry picked from commit 897e7ebc5c7a1c2178691fa281996b19997f1cd5) # Conflicts: # CHANGELOG.md * fix conflict Co-authored-by: Joe Abbey Co-authored-by: Carlos Rodriguez * Update CHANGELOG.md * Update CHANGELOG.md * fix broken link (#2059) (#2096) (cherry picked from commit 7d26a87c96486b8961d485ce08e0748934871914) Co-authored-by: Carlos Rodriguez * gofumpt v3.2.x (#1735) * gofumpt v3.2.x * update Makefile, run make format * remove empty line in comments * chore: fix broken link to bank module spec (backport #2201) (#2208) * chore: fix broken link to bank module spec (#2201) ## 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. - [x] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [x] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit 1a0918d2d116f9fee8c83a037000d425d638ddd4) # Conflicts: # docs/apps/transfer/params.md * fix conflict Co-authored-by: Carlos Rodriguez * feat: adding interchain account address query to controller submodule (backport #2193) (#2291) * feat: adding interchain account address query to controller submodule (#2193) * adding interchain account address query to ica controller * adding basic cli query * satisfy linter, aligning recvr var naming * Apply suggestions from code review Co-authored-by: Carlos Rodriguez * reordering cli args * regenerate protobufs and swagger docs post review suggestions * adding changelog * Update modules/apps/27-interchain-accounts/controller/client/cli/query.go Co-authored-by: Carlos Rodriguez (cherry picked from commit e569045cc5fd467590b5b165f8d494d207111720) # Conflicts: # CHANGELOG.md # docs/client/swagger-ui/swagger.yaml # docs/ibc/proto-docs.md # modules/apps/27-interchain-accounts/controller/keeper/grpc_query.go # modules/apps/27-interchain-accounts/controller/keeper/grpc_query_test.go # modules/apps/27-interchain-accounts/controller/types/query.pb.go * fix merge conflicts Co-authored-by: Damian Nolan Co-authored-by: Colin Axnér <25233464+colin-axner@users.noreply.github.com> * `release/v3.3.x` - Bump SDK to v0.45.8 and Tendermint to v0.34.21 (#2286) * deps: bump SDK to v0.45.8 and tendermint to v0.34.21 * changelog entry * Merge pull request from GHSA-832c-mq9v-367r * fix: use block app hash and tx list to generate interchain account address Generate interchain account addresses using host connection ID, controller PortID, block app hash, and block data hash Update tests to handle non-determinstic address creation Add test case to ensure address generation is block dependent * fix: return error on existing non-interchainaccounts for generated address If an account exists and is not an interchain account return an error Add test cases for existing accounts, both interchain and non interchain account Refactor account tests to be table tests * fix: refactor handshake code to account for block dependent address generation * add more test cases, update error messaging * self review fix * increase test readability * remove msg_server_test.go * fix API breaking changes * self nit * fix tests * fix naming GenerateAddress naming * add test cases for controller side channel reopening * fix cherry-pick conflict * Update modules/apps/27-interchain-accounts/types/account.go Co-authored-by: Damian Nolan Co-authored-by: Damian Nolan * fix: add nil checks for controller and host keeper services (backport #2308) (#2313) * fix: add nil checks for controller and host keeper services (#2308) ## 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. - [ ] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [ ] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit 888c4a0117bfa9753ac4b1ffd1ea81c09867e335) # Conflicts: # CHANGELOG.md # modules/apps/27-interchain-accounts/controller/keeper/migrations.go # modules/apps/27-interchain-accounts/module.go * fix merge conflicts Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> * update changelog for v3.3.0 release * Update CHANGELOG.md * fix broken link/update link (#2338) (#2346) (cherry picked from commit d908b1b445dc52563501d0c4d6d55319c166576f) Co-authored-by: Carlos Rodriguez * Fix ICAControllerKeeper (#2303) (#2332) Co-authored-by: Carlos Rodriguez (cherry picked from commit 280db0ce2c4b8c19ab196c72b547d2e7f02b5bd8) Co-authored-by: Raul Bernal * Adding `paramsKeeper.Subspace(icahosttypes.SubModuleName)` (#2220) (#2324) * Adding `paramsKeeper.Subspace(icahosttypes.SubModuleName)` Adding `paramsKeeper.Subspace(icahosttypes.SubModuleName)` at `initParamsKeeper` func * fix spaces * adding missing paramsKeeper paramsKeeper.Subspace(icacontrollertypes.SubModuleName) Co-authored-by: Carlos Rodriguez Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> (cherry picked from commit 7b26bdaea4802329b4c14e37dae17e3d5dba20b5) Co-authored-by: Raul Bernal * MsgTransferResponse add sequence (backport #2377) (#2465) * MsgTransferResponse add sequence (#2377) ## Description Returns sequence from `sendTransfer`, and returns it with the `MsgTransferResponse`. This is not an API breaking change. Retrieving the sequence at the time of creating the transfer is necessary in the packet forward middleware for correlation with multihop packet flows. https://github.com/strangelove-ventures/packet-forward-middleware/pull/33 https://github.com/strangelove-ventures/ibctest/pull/306 Closes #1969 --- - [x] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [x] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [x] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [x] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) Existing test coverage exercises this new method due to the re-routing of `SendTransfer` through `SendPacketTransfer` - [x] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [x] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [x] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [x] Re-reviewed `Files changed` in the Github PR explorer - [x] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit 33639176c35fa421c4affbbc26f7f6fab10cdfe3) # Conflicts: # docs/ibc/proto-docs.md # modules/apps/transfer/keeper/msg_server.go # modules/apps/transfer/keeper/relay.go # modules/apps/transfer/types/tx.pb.go * fix conflicts Co-authored-by: Andrew Gouin Co-authored-by: crodriguezvega * fixing dead link to sdk v0.44 (#2488) (#2493) Co-authored-by: Carlos Rodriguez (cherry picked from commit a1843f8e240bf1c1de5df234b7d65e3cf6777393) Co-authored-by: Damian Nolan * chore: fix broken links (backport #2511) (#2521) * fix broken links (#2511) Co-authored-by: Carlos Rodriguez (cherry picked from commit 4c452124c33a6b7c2bc5cd83480a97f08d6a7560) # Conflicts: # docs/ibc/apps/apps.md # docs/middleware/ics29-fee/fee-distribution.md # modules/apps/transfer/spec/06_metrics.md * Delete fee-distribution.md * fix conflict * Delete apps.md Co-authored-by: Carlos Rodriguez * Added optional packet metadata to the packet and message types (backport #2305) (#2505) * Added optional packet metadata to the packet and message types (#2305) * added optional packet metadata to the packet and message types * added docs * breaking the api (backports should add a utility function for this) * adding nil metadata on all the calls * added metadata to the cli * added events * breaking api for FungibleTokenPacketData * hex encoding metadata * added abstraction * fixed bad merge * added tests with metadata * added missing metadata to packet for recv * cleaning up metadata on every test * reset metadata * added metadata flag * lint * Update modules/apps/transfer/client/cli/tx.go Co-authored-by: Damian Nolan * fixed bad call in tests Co-authored-by: Damian Nolan (cherry picked from commit 82397d68ff9e11059e4baebb57fa14a73cedd5ed) # Conflicts: # docs/apps/transfer/messages.md # docs/ibc/proto-docs.md # go.mod # go.sum # modules/apps/29-fee/transfer_test.go # modules/apps/transfer/ibc_module.go # modules/apps/transfer/keeper/mbt_relay_test.go # modules/apps/transfer/keeper/relay_test.go # modules/apps/transfer/spec/05_events.md # modules/apps/transfer/types/packet.pb.go # modules/apps/transfer/types/tx.pb.go # proto/ibc/applications/interchain_accounts/controller/v1/tx.proto * fixing conflicts * fix event emission * fix test * fix broken link * revert unnecessary change * revert unnecessary change * remove api breaking changes * removing more api breaking changes * another api breaking change * remove unused code * another api breaking change * another api breaking change * fix tests * remove test Co-authored-by: Nicolas Lara Co-authored-by: Carlos Rodriguez Co-authored-by: Carlos Rodriguez * add entry for #2305 * refactor: adapting transfer metadata bytes field to memo string (backport #2595) (#2597) * refactor: adapting transfer metadata bytes field to memo string (#2595) * adapting transfer metadata bytes field to memo string * updating changelog (cherry picked from commit 05685b3f1bb4241ec04c4768dbb7152273f83a96) # Conflicts: # CHANGELOG.md # docs/ibc/proto-docs.md # go.mod # go.sum # modules/apps/29-fee/transfer_test.go # modules/apps/transfer/client/cli/tx.go # modules/apps/transfer/ibc_module.go # modules/apps/transfer/keeper/mbt_relay_test.go # modules/apps/transfer/keeper/msg_server_test.go # modules/apps/transfer/keeper/relay.go # modules/apps/transfer/keeper/relay_test.go # modules/apps/transfer/transfer_test.go # modules/apps/transfer/types/msgs.go # modules/apps/transfer/types/msgs_test.go # modules/apps/transfer/types/packet.go # modules/apps/transfer/types/packet.pb.go # modules/apps/transfer/types/packet_test.go # modules/apps/transfer/types/tx.pb.go * resolving conflicts Co-authored-by: Damian Nolan * use controller module address instead of module name for NewMsgChannelOpenInit (backport #2568) (#2609) * use controller module address instead of module name for NewMsgChannelOpenInit (#2568) ## Description In controller keeper's `registerInterchainAccount` we execute a `MsgChannelOpenInit` to set up an ica channel. Currently, we have [`icatypes.ModuleName`](https://github.com/cosmos/ibc-go/blob/95cec44c9fb10a2e84a327cdd98c7d588f091f42/modules/apps/27-interchain-accounts/controller/keeper/account.go#L63) as the msg signer. This is not good because the [`ValidateBasic()`](https://github.com/cosmos/ibc-go/blob/692790402a033c1d9647c1e1eded5351332283bf/modules/core/04-channel/types/msgs.go#L34) of `MsgChannelOpenInit` has check if msg signer is bech32 or not. ref #2566 closes: #2559 --- 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. - [ ] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/main/docs/docs/building-modules/10-structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [ ] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit 6105db491a0a1c5a1e4358494dc186401ed1eb26) # Conflicts: # modules/apps/27-interchain-accounts/controller/keeper/account.go * resolving conflicts Co-authored-by: khanh-notional <50263489+catShaark@users.noreply.github.com> Co-authored-by: Damian Nolan * remove port prefix requirement (backport #2590) (#2632) * remove port prefix requirement (#2590) * remove port prefix requirement * chore: remove depcrated test and fix lint * add changelog entry * Update CHANGELOG.md Co-authored-by: Damian Nolan Co-authored-by: Damian Nolan (cherry picked from commit 5f9966bb7576d30bab0438f982a2ecbd23e53ec0) # Conflicts: # modules/apps/27-interchain-accounts/host/keeper/handshake.go * fix conflict Co-authored-by: LaurensKubat <32776056+LaurensKubat@users.noreply.github.com> Co-authored-by: crodriguezvega * release/v3.4.x: bump to SDK v0.45.10 (#2589) * use authtypes.NewModuleAddress * bumps * retract former v3s * fixes * rationale * discard changes to account.go * review comments Co-authored-by: catShaark Co-authored-by: Carlos Rodriguez Co-authored-by: khanh-notional <50263489+catShaark@users.noreply.github.com> * fix: skip emission of unpopulated memo field in ics20 (backport #2651) (#2653) * fix: skip emission of unpopulated memo field in ics20 (#2651) ## Description By setting `EmitDefaults` to false, we will not include an empty memo field in the marshaled json bytes closes: #2645 --- 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. - [x] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/main/docs/docs/building-modules/10-structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [ ] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [ ] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit 393247bff61c225a29fab7aa002877e6653ab415) * Update modules/apps/transfer/types/codec_test.go * fix: merge conflict build Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> Co-authored-by: Damian Nolan * add check send enabled (backport #2679) (#2688) * add check send enabled (#2679) * add check send enabled * changelog (cherry picked from commit b1f494c643280c73f3696461abbb11a39303b190) # Conflicts: # CHANGELOG.md # modules/apps/transfer/keeper/msg_server.go # modules/apps/transfer/keeper/msg_server_test.go * fix conflicts * fix return value * fix build Co-authored-by: Carlos Rodriguez Co-authored-by: Carlos Rodriguez * prepare changelog for v3.4.0 release * typo in tag link * Update CHANGELOG.md * Update incorrect PR link in changelog regarding "check x/bank send enabled" fix (backport #2714) (#2716) * Update check x/bank sendEnabled link in changelog (#2714) Co-authored-by: Cian Hatton (cherry picked from commit 7249d8231e936403dc780bb49ed3c1df3642793e) # Conflicts: # CHANGELOG.md * fix conflicts Co-authored-by: Calvin Lau <38898718+calvinaco@users.noreply.github.com> Co-authored-by: Colin Axnér <25233464+colin-axner@users.noreply.github.com> * docs: add missing set order functions for ICA (backport #2740) (#2754) * add missing set order functions for ica (#2740) Co-authored-by: Carlos Rodriguez (cherry picked from commit f54143e91fdda13f2138bf8d99fa8e5cbd8c9de1) # Conflicts: # docs/middleware/ics29-fee/integration.md * Delete integration.md Co-authored-by: Carlos Rodriguez * tests compiles * 1. remove allow_update_after_expiry & allow_update_after_misbehaviour as it became deprecated 2. update proposal_handle following v3.4.x upgrade 3. delete fraction as it not used anymore 4. fix test function CreateDMClientHeader 5. add more checks and errors to header Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Carlos Rodriguez Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: khanh <50263489+catShaark@users.noreply.github.com> Co-authored-by: Damian Nolan Co-authored-by: vuong <56973102+nguyenvuong1122000@users.noreply.github.com> Co-authored-by: chatton Co-authored-by: Cian Hatton Co-authored-by: Charly Co-authored-by: Carlos Rodriguez Co-authored-by: Sean King Co-authored-by: Dan Lynch Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com> Co-authored-by: Devashish Dixit Co-authored-by: Joe Abbey Co-authored-by: Jacob Gadikian Co-authored-by: Raul Bernal Co-authored-by: Andrew Gouin Co-authored-by: Nicolas Lara Co-authored-by: LaurensKubat <32776056+LaurensKubat@users.noreply.github.com> Co-authored-by: catShaark Co-authored-by: Calvin Lau <38898718+calvinaco@users.noreply.github.com> Co-authored-by: Michael Tsitrin --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- .github/workflows/codeql-analysis.yml | 81 +++ .github/workflows/release.yml | 2 +- .github/workflows/test.yml | 14 +- CHANGELOG.md | 120 +++- Makefile | 6 +- docs/.vuepress/config.js | 17 + docs/DOCS_README.md | 2 +- docs/apps/interchain-accounts/integration.md | 16 +- docs/apps/interchain-accounts/parameters.md | 8 + .../adr-026-ibc-client-recovery-mechanisms.md | 17 +- docs/client/swagger-ui/swagger.yaml | 531 +++++++++++++- docs/ibc/apps.md | 2 +- docs/ibc/integration.md | 2 +- docs/ibc/overview.md | 2 +- docs/ibc/proposals.md | 3 +- docs/ibc/proto-docs.md | 216 +++++- docs/ibc/relayer.md | 2 +- docs/ibc/upgrades/developer-guide.md | 2 +- .../migrations/support-denoms-with-slashes.md | 80 +++ docs/migrations/v2-to-v3.md | 2 +- docs/versions | 1 + go.mod | 96 +-- go.sum | 609 ++++------------ .../controller/client/cli/cli.go | 1 + .../controller/client/cli/query.go | 34 + .../controller/ibc_module_test.go | 35 +- .../controller/keeper/account.go | 5 +- .../controller/keeper/genesis_test.go | 11 +- .../controller/keeper/grpc_query.go | 29 +- .../controller/keeper/grpc_query_test.go | 69 ++ .../controller/keeper/handshake.go | 1 - .../controller/keeper/handshake_test.go | 51 +- .../controller/keeper/keeper.go | 1 - .../controller/keeper/keeper_test.go | 16 +- .../controller/keeper/relay_test.go | 4 +- .../controller/types/params.go | 6 +- .../controller/types/query.pb.go | 488 ++++++++++++- .../controller/types/query.pb.gw.go | 120 ++++ .../host/ibc_module_test.go | 82 +-- .../host/keeper/account.go | 25 + .../host/keeper/account_test.go | 33 - .../host/keeper/genesis_test.go | 10 +- .../host/keeper/handshake.go | 27 +- .../host/keeper/handshake_test.go | 150 +++- .../host/keeper/keeper.go | 1 - .../host/keeper/keeper_test.go | 16 +- .../host/keeper/relay_test.go | 39 ++ .../host/types/ack_test.go | 1 - .../27-interchain-accounts/host/types/keys.go | 5 + .../27-interchain-accounts/module_test.go | 2 - .../27-interchain-accounts/types/account.go | 16 +- .../types/account_test.go | 6 +- .../27-interchain-accounts/types/codec.go | 14 +- .../types/codec_test.go | 1 - .../27-interchain-accounts/types/errors.go | 1 + .../types/genesis_test.go | 12 +- .../apps/27-interchain-accounts/types/keys.go | 3 + .../types/metadata_test.go | 3 - .../27-interchain-accounts/types/packet.go | 2 +- .../types/packet_test.go | 11 +- modules/apps/transfer/client/cli/tx.go | 9 + modules/apps/transfer/ibc_module.go | 3 + modules/apps/transfer/ibc_module_test.go | 6 - modules/apps/transfer/keeper/grpc_query.go | 15 +- .../apps/transfer/keeper/grpc_query_test.go | 42 +- modules/apps/transfer/keeper/keeper.go | 1 - .../apps/transfer/keeper/mbt_relay_test.go | 2 +- modules/apps/transfer/keeper/migrations.go | 58 ++ .../apps/transfer/keeper/migrations_test.go | 121 ++++ modules/apps/transfer/keeper/msg_server.go | 10 +- .../apps/transfer/keeper/msg_server_test.go | 96 +++ modules/apps/transfer/keeper/relay.go | 54 +- modules/apps/transfer/keeper/relay_test.go | 107 ++- modules/apps/transfer/module.go | 7 +- .../apps/transfer/simulation/genesis_test.go | 1 - modules/apps/transfer/spec/04_messages.md | 1 + modules/apps/transfer/spec/05_events.md | 4 + modules/apps/transfer/spec/06_metrics.md | 2 +- modules/apps/transfer/spec/07_params.md | 3 +- modules/apps/transfer/types/ack_test.go | 1 - modules/apps/transfer/types/codec.go | 33 + modules/apps/transfer/types/codec_test.go | 26 + modules/apps/transfer/types/coin.go | 1 - modules/apps/transfer/types/events.go | 1 + .../apps/transfer/types/expected_keepers.go | 1 + modules/apps/transfer/types/msgs.go | 1 + modules/apps/transfer/types/packet.go | 2 +- modules/apps/transfer/types/packet.pb.go | 86 ++- modules/apps/transfer/types/query.pb.go | 519 ++++++++++++-- modules/apps/transfer/types/query.pb.gw.go | 120 ++++ modules/apps/transfer/types/trace.go | 61 +- modules/apps/transfer/types/trace_test.go | 63 +- modules/apps/transfer/types/tx.pb.go | 147 +++- modules/core/02-client/client/cli/cli.go | 1 + modules/core/02-client/client/cli/query.go | 43 ++ modules/core/02-client/keeper/client_test.go | 3 - modules/core/02-client/keeper/grpc_query.go | 41 +- .../core/02-client/keeper/grpc_query_test.go | 183 +++-- .../core/02-client/keeper/proposal_test.go | 4 +- modules/core/02-client/legacy/v100/genesis.go | 2 - .../02-client/legacy/v100/genesis_test.go | 3 - .../core/02-client/proposal_handler_test.go | 1 - modules/core/02-client/types/client_test.go | 4 +- modules/core/02-client/types/codec_test.go | 1 - modules/core/02-client/types/encoding_test.go | 2 - modules/core/02-client/types/genesis.go | 1 - modules/core/02-client/types/height_test.go | 1 - modules/core/02-client/types/msgs.go | 7 +- modules/core/02-client/types/msgs_test.go | 2 - modules/core/02-client/types/proposal_test.go | 2 - modules/core/02-client/types/query.pb.go | 663 ++++++++++++++++-- modules/core/02-client/types/query.pb.gw.go | 116 +++ .../core/03-connection/client/utils/utils.go | 2 - modules/core/03-connection/keeper/events.go | 2 +- .../core/03-connection/keeper/grpc_query.go | 2 - .../03-connection/keeper/grpc_query_test.go | 12 +- .../core/03-connection/keeper/handshake.go | 4 +- .../03-connection/keeper/handshake_test.go | 2 - .../core/03-connection/keeper/keeper_test.go | 1 - modules/core/03-connection/types/codec.go | 14 +- .../core/03-connection/types/genesis_test.go | 1 - modules/core/03-connection/types/msgs.go | 4 + modules/core/03-connection/types/msgs_test.go | 12 +- .../core/03-connection/types/version_test.go | 1 - modules/core/04-channel/client/utils/utils.go | 2 - modules/core/04-channel/keeper/events.go | 2 + modules/core/04-channel/keeper/grpc_query.go | 8 +- .../core/04-channel/keeper/grpc_query_test.go | 21 +- modules/core/04-channel/keeper/handshake.go | 2 +- modules/core/04-channel/keeper/packet_test.go | 2 - .../core/04-channel/keeper/timeout_test.go | 1 - modules/core/04-channel/types/events.go | 1 + modules/core/04-channel/types/msgs_test.go | 3 +- modules/core/04-channel/types/tx.pb.go | 265 ++++--- .../23-commitment/types/commitment_test.go | 3 +- modules/core/23-commitment/types/merkle.go | 6 +- .../core/23-commitment/types/merkle_test.go | 2 - modules/core/24-host/doc.go | 1 - modules/core/keeper/grpc_query.go | 5 + modules/core/keeper/keeper_test.go | 5 - modules/core/keeper/msg_server.go | 13 +- modules/core/keeper/msg_server_test.go | 5 +- .../01-dymint/types/client_state.go | 18 +- .../01-dymint/types/client_state_test.go | 28 +- .../01-dymint/types/consensus_state_test.go | 31 +- .../01-dymint/types/dymint.pb.go | 215 ++---- .../light-clients/01-dymint/types/fraction.go | 21 - .../light-clients/01-dymint/types/header.go | 7 + .../01-dymint/types/header_test.go | 11 + .../types/misbehaviour_handle_test.go | 34 +- .../01-dymint/types/proposal_handle.go | 39 +- .../01-dymint/types/proposal_handle_test.go | 216 ++---- .../light-clients/01-dymint/types/store.go | 1 - .../light-clients/01-dymint/types/update.go | 5 + .../01-dymint/types/update_test.go | 40 +- .../light-clients/01-dymint/types/upgrade.go | 1 - .../01-dymint/types/upgrade_test.go | 12 +- .../06-solomachine/types/client_state_test.go | 3 - .../06-solomachine/types/codec_test.go | 4 - .../types/consensus_state_test.go | 1 - .../types/misbehaviour_handle.go | 3 - .../types/misbehaviour_handle_test.go | 1 - .../06-solomachine/types/misbehaviour_test.go | 1 - .../06-solomachine/types/proof.go | 3 +- .../06-solomachine/types/proposal_handle.go | 1 - .../06-solomachine/types/update_test.go | 1 - .../07-tendermint/types/client_state_test.go | 5 +- .../types/consensus_state_test.go | 36 +- .../07-tendermint/types/header_test.go | 4 +- .../types/misbehaviour_handle.go | 1 - .../07-tendermint/types/proposal_handle.go | 44 +- .../types/proposal_handle_test.go | 179 ++--- .../07-tendermint/types/store.go | 1 - .../07-tendermint/types/tendermint.pb.go | 144 ++-- .../07-tendermint/types/upgrade.go | 8 +- .../07-tendermint/types/upgrade_test.go | 4 +- .../09-localhost/types/client_state_test.go | 3 - .../09-localhost/types/localhost_test.go | 4 +- .../controller/v1/query.proto | 18 + .../ibc/applications/transfer/v1/query.proto | 19 + proto/ibc/applications/transfer/v1/tx.proto | 7 +- .../ibc/applications/transfer/v2/packet.proto | 2 + proto/ibc/core/channel/v1/tx.proto | 5 +- proto/ibc/core/client/v1/query.proto | 23 + proto/ibc/lightclients/dymint/dymint.proto | 9 +- .../tendermint/v1/tendermint.proto | 11 +- testing/chain.go | 18 +- testing/chain_dymint.go | 26 +- testing/chain_tendermint.go | 5 - testing/events.go | 1 - testing/mock/ibc_module.go | 1 - testing/mock/ibc_module_test.go | 2 +- testing/simapp/ante_handler.go | 2 +- testing/simapp/app.go | 2 +- testing/simapp/export.go | 2 +- testing/simapp/params/amino.go | 1 + testing/simapp/params/proto.go | 1 + testing/simapp/sim_test.go | 6 +- testing/simapp/simd/cmd/genaccounts_test.go | 3 +- testing/simapp/simd/cmd/root.go | 4 +- testing/simapp/simd/cmd/testnet.go | 9 +- testing/simapp/state.go | 1 - testing/simapp/test_helpers.go | 1 - testing/simapp/utils.go | 4 +- testing/values.go | 10 +- 206 files changed, 5489 insertions(+), 2086 deletions(-) create mode 100644 .github/workflows/codeql-analysis.yml create mode 100644 docs/migrations/support-denoms-with-slashes.md delete mode 100644 modules/apps/27-interchain-accounts/host/keeper/account_test.go create mode 100644 modules/apps/transfer/keeper/migrations.go create mode 100644 modules/apps/transfer/keeper/migrations_test.go create mode 100644 modules/apps/transfer/keeper/msg_server_test.go create mode 100644 modules/apps/transfer/types/codec_test.go delete mode 100644 modules/light-clients/01-dymint/types/fraction.go mode change 100644 => 100755 proto/ibc/lightclients/dymint/dymint.proto diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 435e6a89ca8..4479c1cfc8b 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -20,7 +20,7 @@ write a little note why. - [ ] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. -- [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). +- [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/main/docs/docs/building-modules/10-structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000000..f9a7c4a9230 --- /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: ubuntu-latest + 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@v3 + - uses: technote-space/get-diff-action@v6.1.0 + with: + PATTERNS: | + **/**.go + go.mod + go.sum + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + 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@v2 + 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@v2 + if: env.GIT_DIFF diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f1da879c762..660d68836e2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: - uses: actions/setup-go@v2 with: - go-version: '1.17' + go-version: '1.18' - name: Release uses: goreleaser/goreleaser-action@v2 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 585a3653270..8ed4ccab375 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,13 +20,13 @@ jobs: steps: - uses: actions/setup-go@v2.1.5 with: - go-version: 1.17 + go-version: 1.18 - name: Display go version run: go version - name: install tparse run: | - export GO111MODULE="on" && go get github.com/mfridman/tparse@v0.8.3 - - uses: actions/cache@v2.1.7 + go install github.com/mfridman/tparse@v0.8.3 + - uses: actions/cache@v3 with: path: ~/go/bin key: ${{ runner.os }}-go-tparse-binary @@ -40,8 +40,8 @@ jobs: - uses: actions/checkout@v2.4.0 - uses: actions/setup-go@v2.1.5 with: - go-version: 1.17 - - uses: technote-space/get-diff-action@v6.0.1 + go-version: 1.18 + - uses: technote-space/get-diff-action@v6.1.0 id: git_diff with: PATTERNS: | @@ -88,8 +88,8 @@ jobs: - uses: actions/checkout@v2.4.0 - uses: actions/setup-go@v2.1.5 with: - go-version: 1.17 - - uses: technote-space/get-diff-action@v6.0.1 + go-version: 1.18 + - uses: technote-space/get-diff-action@v6.1.0 with: PATTERNS: | **/**.go diff --git a/CHANGELOG.md b/CHANGELOG.md index eef5b7ecbad..cb99ad65850 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,9 +48,93 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Bug Fixes +## [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) [\#2580](https://github.com/cosmos/ibc-go/issues/2580) 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.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 endpont 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.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.0](https://github.com/cosmos/ibc-go/releases/tag/v3.1.0) - 2022-04-16 + +### 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 @@ -58,7 +142,8 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (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 + +## [v3.0.1](https://github.com/cosmos/ibc-go/releases/tag/v3.0.1) - 2022-04-16 ### Dependencies @@ -74,7 +159,9 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### 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 +* (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. +* (modules/core/04-channel)[\#1919](https://github.com/cosmos/ibc-go/pull/1919) Fixed formatting of sequence for packet "acknowledgement written" logs. + ## [v3.0.0](https://github.com/cosmos/ibc-go/releases/tag/v3.0.0) - 2022-03-15 @@ -136,17 +223,6 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (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.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 @@ -173,14 +249,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (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 +## [v2.2.1](https://github.com/cosmos/ibc-go/releases/tag/v2.2.1) - 2022-04-16 ### Improvements @@ -200,14 +269,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * [\#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 +## [v2.1.1](https://github.com/cosmos/ibc-go/releases/tag/v2.1.1) - 2022-04-16 ### Dependencies diff --git a/Makefile b/Makefile index 0ea7f0c10c5..7780ec33dca 100644 --- a/Makefile +++ b/Makefile @@ -335,10 +335,8 @@ lint-fix: golangci-lint run --fix --out-format=tab --issues-exit-code=0 .PHONY: lint lint-fix -format: - find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "./client/docs/statik/statik.go" -not -path "./tests/mocks/*" -not -name '*.pb.go' | xargs gofmt -w -s - find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "./client/docs/statik/statik.go" -not -path "./tests/mocks/*" -not -name '*.pb.go' | xargs misspell -w - find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "./client/docs/statik/statik.go" -not -path "./tests/mocks/*" -not -name '*.pb.go' | xargs goimports -w -local github.com/cosmos/cosmos-sdk +format: + find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "./client/docs/statik/statik.go" -not -path "./tests/mocks/*" -not -name '*.pb.go' -not -name '*.pb.gw.go' | xargs gofumpt -w .PHONY: format ############################################################################### diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 43bd93590cd..f2630401e61 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -51,6 +51,10 @@ module.exports = { "label": "v1.4.0", "key": "v1.4.0" }, + { + "label": "v1.5.0", + "key": "v1.5.0" + }, { "label": "v2.0.0", "key": "v2.0.0" @@ -63,9 +67,17 @@ module.exports = { "label": "v2.2.0", "key": "v2.2.0" }, + { + "label": "v2.3.0", + "key": "v2.3.0" + }, { "label": "v3.0.0", "key": "v3.0.0" + }, + { + "label": "v3.1.0", + "key": "v3.1.0" } ], topbar: { @@ -169,6 +181,11 @@ module.exports = { { title: "Migrations", children: [ + { + title: "Support transfer of coins whose base denom contains slashes", + directory: false, + path: "/migrations/support-denoms-with-slashes.html" + }, { title: "SDK v0.43 to IBC-Go v1", directory: false, diff --git a/docs/DOCS_README.md b/docs/DOCS_README.md index fb6e4f2acc3..348a4e3c3f1 100644 --- a/docs/DOCS_README.md +++ b/docs/DOCS_README.md @@ -96,7 +96,7 @@ We are using [Algolia](https://www.algolia.com) to power full-text search. This ## Consistency Because the build processes are identical (as is the information contained herein), this file should be kept in sync as -much as possible with its [counterpart in the Cosmos SDK repo](https://github.com/cosmos/cosmos-sdk/tree/master/docs/DOCS_README.md). +much as possible with its [counterpart in the Cosmos SDK repo](https://github.com/cosmos/cosmos-sdk/blob/main/docs/README.md). ### Update and Build the RPC docs diff --git a/docs/apps/interchain-accounts/integration.md b/docs/apps/interchain-accounts/integration.md index eca5ab47eab..e6bb73ebe42 100644 --- a/docs/apps/interchain-accounts/integration.md +++ b/docs/apps/interchain-accounts/integration.md @@ -116,8 +116,22 @@ app.moduleManager = module.NewManager( ... +// 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.mm.SetOrderInitGenesis( +app.moduleManager.SetOrderInitGenesis( ... icatypes.ModuleName, ... diff --git a/docs/apps/interchain-accounts/parameters.md b/docs/apps/interchain-accounts/parameters.md index f4ddcbc1160..8f985b9293e 100644 --- a/docs/apps/interchain-accounts/parameters.md +++ b/docs/apps/interchain-accounts/parameters.md @@ -47,4 +47,12 @@ For example, a Cosmos SDK based chain that elects to provide hosted Interchain A "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. + +``` +"params": { + "host_enabled": true, + "allow_messages": ["*"] +} ``` \ No newline at end of file diff --git a/docs/architecture/adr-026-ibc-client-recovery-mechanisms.md b/docs/architecture/adr-026-ibc-client-recovery-mechanisms.md index c40e3b08cd5..bec25a3aad9 100644 --- a/docs/architecture/adr-026-ibc-client-recovery-mechanisms.md +++ b/docs/architecture/adr-026-ibc-client-recovery-mechanisms.md @@ -6,6 +6,8 @@ - 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 ## Status @@ -35,22 +37,24 @@ Two-thirds of the validator set (the quorum for governance, module participation 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_governance_override_after_expiry` (boolean, default false) + 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_governance_override_after_misbehaviour` (boolean, default false) + 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 type, `ClientUpdateProposal`, in the `x/ibc` module 1. Extend the base `Proposal` with two client identifiers (`string`). 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 identitical 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, if and only if: - 1. `allow_governance_override_after_expiry` is true and the client has expired (`Expired()` returns true) - 1. `allow_governance_override_after_misbehaviour` is true and the client has been frozen (`Frozen()` returns true) - 1. In this case, additionally, the client is unfrozen by calling `Unfreeze()` + 1. If this governance proposal passes, the client on trial will be updated to the latest state of the substitute. + 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 initally 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. + + Note that this should NOT be lightly updated, as there may be a gap in time between when misbehaviour has occured 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. @@ -62,7 +66,6 @@ This ADR does not address planned upgrades, which are handled separately as per - Establishes a mechanism for client recovery in the case of expiry - Establishes a mechanism for client recovery in the case of misbehaviour -- Clients can elect to disallow this recovery mechanism if they do not wish to allow for it - Constructing an ClientUpdate Proposal is as difficult as creating a new client ### Negative diff --git a/docs/client/swagger-ui/swagger.yaml b/docs/client/swagger-ui/swagger.yaml index 74f6ee85a9e..084926dcc27 100644 --- a/docs/client/swagger-ui/swagger.yaml +++ b/docs/client/swagger-ui/swagger.yaml @@ -4,6 +4,59 @@ info: 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. @@ -300,6 +353,56 @@ paths: 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. @@ -2135,6 +2238,331 @@ paths: + 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 + format: 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 + 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 + title: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently + 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); + } + + 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 := ptypes.MarshalAny(foo) + ... + foo := &pb.Foo{} + if err := ptypes.UnmarshalAny(any, 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 ==== @@ -2422,7 +2850,6 @@ paths: format: byte title: merkle proof of existence proof_height: - title: height at which the proof was retrieved type: object properties: revision_number: @@ -2450,6 +2877,13 @@ paths: RevisionHeight gets reset + 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 @@ -10076,6 +10510,15 @@ definitions: QueryConnectionsResponse is the response type for the Query/DenomTraces 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 @@ -10111,6 +10554,14 @@ definitions: 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: @@ -11005,6 +11456,76 @@ definitions: 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 + 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 + title: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently + 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: @@ -11175,7 +11696,6 @@ definitions: format: byte title: merkle proof of existence proof_height: - title: height at which the proof was retrieved type: object properties: revision_number: @@ -11202,6 +11722,13 @@ definitions: RevisionHeight gets reset + 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 diff --git a/docs/ibc/apps.md b/docs/ibc/apps.md index f729d7dfd71..ad6a2223ce4 100644 --- a/docs/ibc/apps.md +++ b/docs/ibc/apps.md @@ -474,4 +474,4 @@ callbacks](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/ibc_ ## Next {hide} -Learn about [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/intro.md) {hide} +Learn about [building modules](https://github.com/cosmos/cosmos-sdk/blob/main/docs/docs/building-modules/01-intro.md) {hide} diff --git a/docs/ibc/integration.md b/docs/ibc/integration.md index 09c1d2d2de9..3dd8b0a74a6 100644 --- a/docs/ibc/integration.md +++ b/docs/ibc/integration.md @@ -139,7 +139,7 @@ func NewApp(...args) *App { ### 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://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/simulator.md). +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://github.com/cosmos/cosmos-sdk/blob/main/docs/docs/building-modules/13-simulator.md). ```go // app.go diff --git a/docs/ibc/overview.md b/docs/ibc/overview.md index 53ad64e08e8..a09a60f8118 100644 --- a/docs/ibc/overview.md +++ b/docs/ibc/overview.md @@ -136,7 +136,7 @@ Proofs are passed from core IBC to light-clients as bytes. It is up to light cli [ICS-24 Host State Machine Requirements](https://github.com/cosmos/ics/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](https://github.com/cosmos/cosmos-sdk/blob/master/docs/core/ocap.md) +### [Capabilities](https://github.com/cosmos/cosmos-sdk/blob/main/docs/docs/core/10-ocap.md) 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 diff --git a/docs/ibc/proposals.md b/docs/ibc/proposals.md index c2cb34860b2..f6bf351a705 100644 --- a/docs/ibc/proposals.md +++ b/docs/ibc/proposals.md @@ -46,7 +46,6 @@ See also the relevant documentation: [ADR-026, IBC client recovery mechanisms](. ### Preconditions - The chain is updated with ibc-go >= v1.1.0. -- Recovery parameters are set to `true` for the Tendermint light client (this determines if a governance proposal can be used). If the recovery parameters are set to `false`, recovery will require custom migration code. - The client identifier of an active client for the same counterparty chain. - The governance deposit. @@ -67,7 +66,7 @@ Check if the client is attached to the expected `chain-id`. For example, for an } ``` -The client is attached to the expected Akash `chain-id` and the recovery parameters (`allow_update_after_expiry` and `allow_update_after_misbehaviour`) are set to `true`. +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 diff --git a/docs/ibc/proto-docs.md b/docs/ibc/proto-docs.md index 877cc37557c..7f31c1a7013 100644 --- a/docs/ibc/proto-docs.md +++ b/docs/ibc/proto-docs.md @@ -37,6 +37,8 @@ - [QueryDenomTraceResponse](#ibc.applications.transfer.v1.QueryDenomTraceResponse) - [QueryDenomTracesRequest](#ibc.applications.transfer.v1.QueryDenomTracesRequest) - [QueryDenomTracesResponse](#ibc.applications.transfer.v1.QueryDenomTracesResponse) + - [QueryEscrowAddressRequest](#ibc.applications.transfer.v1.QueryEscrowAddressRequest) + - [QueryEscrowAddressResponse](#ibc.applications.transfer.v1.QueryEscrowAddressResponse) - [QueryParamsRequest](#ibc.applications.transfer.v1.QueryParamsRequest) - [QueryParamsResponse](#ibc.applications.transfer.v1.QueryParamsResponse) @@ -145,6 +147,8 @@ - [QueryClientStatesResponse](#ibc.core.client.v1.QueryClientStatesResponse) - [QueryClientStatusRequest](#ibc.core.client.v1.QueryClientStatusRequest) - [QueryClientStatusResponse](#ibc.core.client.v1.QueryClientStatusResponse) + - [QueryConsensusStateHeightsRequest](#ibc.core.client.v1.QueryConsensusStateHeightsRequest) + - [QueryConsensusStateHeightsResponse](#ibc.core.client.v1.QueryConsensusStateHeightsResponse) - [QueryConsensusStateRequest](#ibc.core.client.v1.QueryConsensusStateRequest) - [QueryConsensusStateResponse](#ibc.core.client.v1.QueryConsensusStateResponse) - [QueryConsensusStatesRequest](#ibc.core.client.v1.QueryConsensusStatesRequest) @@ -217,6 +221,13 @@ - [ibc/core/types/v1/genesis.proto](#ibc/core/types/v1/genesis.proto) - [GenesisState](#ibc.core.types.v1.GenesisState) +- [ibc/lightclients/dymint/dymint.proto](#ibc/lightclients/dymint/dymint.proto) + - [ClientState](#ibc.lightclients.dymint.ClientState) + - [ConsensusState](#ibc.lightclients.dymint.ConsensusState) + - [Fraction](#ibc.lightclients.dymint.Fraction) + - [Header](#ibc.lightclients.dymint.Header) + - [Misbehaviour](#ibc.lightclients.dymint.Misbehaviour) + - [ibc/lightclients/localhost/v1/localhost.proto](#ibc/lightclients/localhost/v1/localhost.proto) - [ClientState](#ibc.lightclients.localhost.v1.ClientState) @@ -692,6 +703,37 @@ method. + + +### QueryEscrowAddressRequest +QueryEscrowAddressRequest is the request type for the EscrowAddress RPC method. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `port_id` | [string](#string) | | unique port identifier | +| `channel_id` | [string](#string) | | unique channel identifier | + + + + + + + + +### QueryEscrowAddressResponse +QueryEscrowAddressResponse is the response type of the EscrowAddress RPC method. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `escrow_address` | [string](#string) | | the escrow account address | + + + + + + ### QueryParamsRequest @@ -734,6 +776,7 @@ Query provides defines the gRPC querier service. | `DenomTraces` | [QueryDenomTracesRequest](#ibc.applications.transfer.v1.QueryDenomTracesRequest) | [QueryDenomTracesResponse](#ibc.applications.transfer.v1.QueryDenomTracesResponse) | DenomTraces queries all denomination traces. | GET|/ibc/apps/transfer/v1/denom_traces| | `Params` | [QueryParamsRequest](#ibc.applications.transfer.v1.QueryParamsRequest) | [QueryParamsResponse](#ibc.applications.transfer.v1.QueryParamsResponse) | Params queries all parameters of the ibc-transfer module. | GET|/ibc/apps/transfer/v1/params| | `DenomHash` | [QueryDenomHashRequest](#ibc.applications.transfer.v1.QueryDenomHashRequest) | [QueryDenomHashResponse](#ibc.applications.transfer.v1.QueryDenomHashResponse) | DenomHash queries a denomination hash information. | GET|/ibc/apps/transfer/v1/denom_hashes/{trace}| +| `EscrowAddress` | [QueryEscrowAddressRequest](#ibc.applications.transfer.v1.QueryEscrowAddressRequest) | [QueryEscrowAddressResponse](#ibc.applications.transfer.v1.QueryEscrowAddressResponse) | EscrowAddress returns the escrow address for a particular port and channel id. | GET|/ibc/apps/transfer/v1/channels/{channel_id}/ports/{port_id}/escrow_address| @@ -910,6 +953,7 @@ https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transf | `receiver` | [string](#string) | | the recipient address on the destination chain | | `timeout_height` | [ibc.core.client.v1.Height](#ibc.core.client.v1.Height) | | Timeout height relative to the current block height. The timeout is disabled when set to 0. | | `timeout_timestamp` | [uint64](#uint64) | | Timeout timestamp in absolute nanoseconds since unix epoch. The timeout is disabled when set to 0. | +| `memo` | [string](#string) | | optional memo | @@ -922,6 +966,11 @@ https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transf MsgTransferResponse defines the Msg/Transfer response type. +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `sequence` | [uint64](#uint64) | | sequence number of the transfer packet sent | + + @@ -966,6 +1015,7 @@ https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transf | `amount` | [string](#string) | | the token amount to be transferred | | `sender` | [string](#string) | | the sender address | | `receiver` | [string](#string) | | the recipient address on the destination chain | +| `memo` | [string](#string) | | optional memo | @@ -1898,6 +1948,7 @@ MsgChannelOpenInitResponse defines the Msg/ChannelOpenInit response type. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | `channel_id` | [string](#string) | | | +| `version` | [string](#string) | | | @@ -1933,6 +1984,11 @@ value will be ignored by core IBC. MsgChannelOpenTryResponse defines the Msg/ChannelOpenTry response type. +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `version` | [string](#string) | | | + + @@ -2284,6 +2340,40 @@ method. It returns the current status of the IBC client. + + +### QueryConsensusStateHeightsRequest +QueryConsensusStateHeightsRequest is the request type for Query/ConsensusStateHeights +RPC method. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `client_id` | [string](#string) | | client identifier | +| `pagination` | [cosmos.base.query.v1beta1.PageRequest](#cosmos.base.query.v1beta1.PageRequest) | | pagination request | + + + + + + + + +### QueryConsensusStateHeightsResponse +QueryConsensusStateHeightsResponse is the response type for the +Query/ConsensusStateHeights RPC method + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `consensus_state_heights` | [Height](#ibc.core.client.v1.Height) | repeated | consensus state heights | +| `pagination` | [cosmos.base.query.v1beta1.PageResponse](#cosmos.base.query.v1beta1.PageResponse) | | pagination response | + + + + + + ### QueryConsensusStateRequest @@ -2427,6 +2517,7 @@ Query provides defines the gRPC querier service | `ClientStates` | [QueryClientStatesRequest](#ibc.core.client.v1.QueryClientStatesRequest) | [QueryClientStatesResponse](#ibc.core.client.v1.QueryClientStatesResponse) | ClientStates queries all the IBC light clients of a chain. | GET|/ibc/core/client/v1/client_states| | `ConsensusState` | [QueryConsensusStateRequest](#ibc.core.client.v1.QueryConsensusStateRequest) | [QueryConsensusStateResponse](#ibc.core.client.v1.QueryConsensusStateResponse) | ConsensusState queries a consensus state associated with a client state at a given height. | GET|/ibc/core/client/v1/consensus_states/{client_id}/revision/{revision_number}/height/{revision_height}| | `ConsensusStates` | [QueryConsensusStatesRequest](#ibc.core.client.v1.QueryConsensusStatesRequest) | [QueryConsensusStatesResponse](#ibc.core.client.v1.QueryConsensusStatesResponse) | ConsensusStates queries all the consensus state associated with a given client. | GET|/ibc/core/client/v1/consensus_states/{client_id}| +| `ConsensusStateHeights` | [QueryConsensusStateHeightsRequest](#ibc.core.client.v1.QueryConsensusStateHeightsRequest) | [QueryConsensusStateHeightsResponse](#ibc.core.client.v1.QueryConsensusStateHeightsResponse) | ConsensusStateHeights queries the height of every consensus states associated with a given client. | GET|/ibc/core/client/v1/consensus_states/{client_id}/heights| | `ClientStatus` | [QueryClientStatusRequest](#ibc.core.client.v1.QueryClientStatusRequest) | [QueryClientStatusResponse](#ibc.core.client.v1.QueryClientStatusResponse) | Status queries the status of an IBC client. | GET|/ibc/core/client/v1/client_status/{client_id}| | `ClientParams` | [QueryClientParamsRequest](#ibc.core.client.v1.QueryClientParamsRequest) | [QueryClientParamsResponse](#ibc.core.client.v1.QueryClientParamsResponse) | ClientParams queries all parameters of the ibc client. | GET|/ibc/client/v1/params| | `UpgradedClientState` | [QueryUpgradedClientStateRequest](#ibc.core.client.v1.QueryUpgradedClientStateRequest) | [QueryUpgradedClientStateResponse](#ibc.core.client.v1.QueryUpgradedClientStateResponse) | UpgradedClientState queries an Upgraded IBC light client. | GET|/ibc/core/client/v1/upgraded_client_states| @@ -3242,6 +3333,127 @@ GenesisState defines the ibc module's genesis state. + + + + + + + + + + + +

Top

+ +## ibc/lightclients/dymint/dymint.proto + + + + + +### ClientState +ClientState from Dymint tracks the current validator set, latest height, +and a possible frozen height. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `chain_id` | [string](#string) | | | +| `trust_level` | [Fraction](#ibc.lightclients.dymint.Fraction) | | | +| `trusting_period` | [google.protobuf.Duration](#google.protobuf.Duration) | | duration of the period since the LastestTimestamp during which the submitted headers are valid for upgrade | +| `unbonding_period` | [google.protobuf.Duration](#google.protobuf.Duration) | | duration of the staking unbonding period | +| `max_clock_drift` | [google.protobuf.Duration](#google.protobuf.Duration) | | defines how much new (untrusted) header's Time can drift into the future. | +| `frozen_height` | [ibc.core.client.v1.Height](#ibc.core.client.v1.Height) | | Block height when the client was frozen due to a misbehaviour | +| `latest_height` | [ibc.core.client.v1.Height](#ibc.core.client.v1.Height) | | Latest height the client was updated to | +| `proof_specs` | [ics23.ProofSpec](#ics23.ProofSpec) | repeated | Proof specifications used in verifying counterparty state | +| `upgrade_path` | [string](#string) | repeated | 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"}` | + + + + + + + + +### ConsensusState +ConsensusState defines the consensus state from Dymint. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `timestamp` | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | timestamp that corresponds to the block height in which the ConsensusState was stored. | +| `root` | [ibc.core.commitment.v1.MerkleRoot](#ibc.core.commitment.v1.MerkleRoot) | | commitment root (i.e app hash) | +| `next_validators_hash` | [bytes](#bytes) | | | + + + + + + + + +### Fraction +Fraction defines the protobuf message type for tmmath.Fraction that only +supports positive values. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `numerator` | [uint64](#uint64) | | | +| `denominator` | [uint64](#uint64) | | | + + + + + + + + +### Header +Header defines the Dymint client consensus Header. +It encapsulates all the information necessary to update from a trusted +Dymint 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. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `signed_header` | [tendermint.types.SignedHeader](#tendermint.types.SignedHeader) | | | +| `validator_set` | [tendermint.types.ValidatorSet](#tendermint.types.ValidatorSet) | | | +| `trusted_height` | [ibc.core.client.v1.Height](#ibc.core.client.v1.Height) | | | +| `trusted_validators` | [tendermint.types.ValidatorSet](#tendermint.types.ValidatorSet) | | | + + + + + + + + +### Misbehaviour +Misbehaviour is a wrapper over two conflicting Headers +that implements Misbehaviour interface expected by ICS-02 + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `client_id` | [string](#string) | | | +| `header_1` | [Header](#ibc.lightclients.dymint.Header) | | | +| `header_2` | [Header](#ibc.lightclients.dymint.Header) | | | + + + + + @@ -3946,8 +4158,8 @@ and a possible frozen height. | `latest_height` | [ibc.core.client.v1.Height](#ibc.core.client.v1.Height) | | Latest height the client was updated to | | `proof_specs` | [ics23.ProofSpec](#ics23.ProofSpec) | repeated | Proof specifications used in verifying counterparty state | | `upgrade_path` | [string](#string) | repeated | 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"}` | -| `allow_update_after_expiry` | [bool](#bool) | | This flag, when set to true, will allow governance to recover a client which has expired | -| `allow_update_after_misbehaviour` | [bool](#bool) | | This flag, when set to true, will allow governance to unfreeze a client whose chain has experienced a misbehaviour event | +| `allow_update_after_expiry` | [bool](#bool) | | **Deprecated.** allow_update_after_expiry is deprecated | +| `allow_update_after_misbehaviour` | [bool](#bool) | | **Deprecated.** allow_update_after_misbehaviour is deprecated | diff --git a/docs/ibc/relayer.md b/docs/ibc/relayer.md index 6705ac6aa0a..8327ebfb8bf 100644 --- a/docs/ibc/relayer.md +++ b/docs/ibc/relayer.md @@ -7,7 +7,7 @@ order: 6 ## Pre-requisites Readings - [IBC Overview](./overview.md) {prereq} -- [Events](https://github.com/cosmos/cosmos-sdk/blob/master/docs/core/events.md) {prereq} +- [Events](https://github.com/cosmos/cosmos-sdk/blob/main/docs/docs/core/08-events.md) {prereq} ## Events diff --git a/docs/ibc/upgrades/developer-guide.md b/docs/ibc/upgrades/developer-guide.md index d41b3346d4f..73a19b93664 100644 --- a/docs/ibc/upgrades/developer-guide.md +++ b/docs/ibc/upgrades/developer-guide.md @@ -36,7 +36,7 @@ Developers must ensure that the new client adopts all of the new Client paramete 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`, `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 `TrustingPeriod`, `TrustLevel`, `MaxClockDrift`, etc). +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: diff --git a/docs/migrations/support-denoms-with-slashes.md b/docs/migrations/support-denoms-with-slashes.md new file mode 100644 index 00000000000..0447cf57d99 --- /dev/null +++ b/docs/migrations/support-denoms-with-slashes.md @@ -0,0 +1,80 @@ +# 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/migrations/v2-to-v3.md b/docs/migrations/v2-to-v3.md index 275e9d7356d..a7ad1fb10fc 100644 --- a/docs/migrations/v2-to-v3.md +++ b/docs/migrations/v2-to-v3.md @@ -69,7 +69,7 @@ For example, if a chain chooses not to integrate a controller submodule, it may #### Add `StoreUpgrades` for ICS27 module -For ICS27 it is also necessary to [manually add store upgrades](https://docs.cosmos.network/v0.44/core/upgrade.html#add-storeupgrades-for-new-modules) for the new ICS27 module and then configure the store loader to apply those upgrades in `app.go`: +For ICS27 it is also necessary to [manually add store upgrades](https://docs.cosmos.network/v0.45/core/upgrade.html#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) { diff --git a/docs/versions b/docs/versions index 2780084203d..7771cabc308 100644 --- a/docs/versions +++ b/docs/versions @@ -1,3 +1,4 @@ +release/v3.1.x v3.1.0 release/v3.0.x v3.0.0 release/v2.3.x v2.3.0 release/v2.2.x v2.2.0 diff --git a/go.mod b/go.mod index a95188cd377..2461465f59a 100644 --- a/go.mod +++ b/go.mod @@ -1,28 +1,28 @@ -go 1.17 +go 1.18 module github.com/cosmos/ibc-go/v3 -replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 +retract [v3.0.0, v3.3.0] // depends on SDK version without dragonberry fix require ( - github.com/armon/go-metrics v0.3.10 + github.com/armon/go-metrics v0.4.0 github.com/confio/ics23/go v0.7.0 - github.com/cosmos/cosmos-sdk v0.45.4 + github.com/cosmos/cosmos-sdk v0.45.10 github.com/gogo/protobuf v1.3.3 github.com/golang/protobuf v1.5.2 github.com/gorilla/mux v1.8.0 github.com/grpc-ecosystem/grpc-gateway v1.16.0 github.com/rakyll/statik v0.1.7 github.com/regen-network/cosmos-proto v0.3.1 - github.com/spf13/cast v1.4.1 - github.com/spf13/cobra v1.4.0 - github.com/spf13/viper v1.10.1 - github.com/stretchr/testify v1.7.1 - github.com/tendermint/tendermint v0.34.19 - github.com/tendermint/tm-db v0.6.6 - google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb - google.golang.org/grpc v1.45.0 - google.golang.org/protobuf v1.28.0 + github.com/spf13/cast v1.5.0 + github.com/spf13/cobra v1.5.0 + github.com/spf13/viper v1.13.0 + github.com/stretchr/testify v1.8.0 + github.com/tendermint/tendermint v0.34.22 + github.com/tendermint/tm-db v0.6.7 + google.golang.org/genproto v0.0.0-20220725144611-272f38e5d71b + google.golang.org/grpc v1.50.0 + google.golang.org/protobuf v1.28.1 gopkg.in/yaml.v2 v2.4.0 ) @@ -34,15 +34,17 @@ require ( github.com/Workiva/go-datastructures v1.0.53 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect - github.com/btcsuite/btcd v0.22.0-beta // indirect + github.com/btcsuite/btcd v0.22.1 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/coinbase/rosetta-sdk-go v0.7.0 // indirect github.com/cosmos/btcutil v1.0.4 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect - github.com/cosmos/iavl v0.17.3 // indirect + github.com/cosmos/gorocksdb v1.2.0 // indirect + github.com/cosmos/iavl v0.19.3 // indirect github.com/cosmos/ledger-cosmos-go v0.11.1 // indirect github.com/cosmos/ledger-go v0.9.2 // indirect + github.com/creachadair/taskgroup v0.3.2 // indirect github.com/danieljoos/wincred v1.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect @@ -52,11 +54,11 @@ require ( github.com/dustin/go-humanize v1.0.0 // indirect github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b // indirect github.com/felixge/httpsnoop v1.0.1 // indirect - github.com/fsnotify/fsnotify v1.5.1 // indirect - github.com/gin-gonic/gin v1.7.0 // indirect + github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/go-kit/kit v0.12.0 // indirect - github.com/go-kit/log v0.2.0 // indirect + github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect + github.com/go-playground/validator/v10 v10.4.1 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/gateway v1.1.0 // indirect github.com/golang/snappy v0.0.3 // indirect @@ -73,49 +75,59 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20210204194344-59a8610d2b87 // indirect github.com/improbable-eng/grpc-web v0.14.1 // indirect - github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/jmhodges/levigo v1.0.0 // indirect github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d // indirect - github.com/klauspost/compress v1.13.6 // indirect - github.com/lib/pq v1.10.4 // indirect - github.com/libp2p/go-buffer-pool v0.0.2 // indirect - github.com/magiconair/properties v1.8.5 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 // indirect + github.com/klauspost/compress v1.15.9 // indirect + github.com/lib/pq v1.10.6 // indirect + github.com/libp2p/go-buffer-pool v0.1.0 // indirect + github.com/magiconair/properties v1.8.6 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect github.com/minio/highwayhash v1.0.2 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mitchellh/mapstructure v1.4.3 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mtibben/percent v0.2.1 // indirect - github.com/pelletier/go-toml v1.9.4 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.12.1 // indirect + github.com/prometheus/client_golang v1.12.2 // indirect github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.32.1 // indirect + github.com/prometheus/common v0.34.0 // indirect github.com/prometheus/procfs v0.7.3 // indirect - github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect + github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rs/cors v1.8.2 // indirect - github.com/rs/zerolog v1.23.0 // indirect - github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa // indirect - github.com/spf13/afero v1.6.0 // indirect + github.com/rs/zerolog v1.27.0 // indirect + github.com/sasha-s/go-deadlock v0.3.1 // indirect + github.com/spf13/afero v1.8.2 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/subosito/gotenv v1.2.0 // indirect + github.com/subosito/gotenv v1.4.1 // indirect github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca // indirect - github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect github.com/tendermint/btcd v0.1.1 // indirect github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 // indirect github.com/tendermint/go-amino v0.16.0 // indirect github.com/zondax/hid v0.9.0 // indirect go.etcd.io/bbolt v1.3.6 // indirect - golang.org/x/crypto v0.0.0-20210915214749-c084706c2272 // indirect - golang.org/x/net v0.0.0-20211208012354-db4efeb81f4b // indirect - golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect - golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect + golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect + golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect + golang.org/x/net v0.0.0-20220812174116-3211cb980234 // indirect + golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 // indirect + golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect golang.org/x/text v0.3.7 // indirect - gopkg.in/ini.v1 v1.66.2 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect nhooyr.io/websocket v1.8.6 // indirect ) + +replace ( + // dragonberry replace for ics23 + github.com/confio/ics23/go => github.com/cosmos/cosmos-sdk/ics23/go v0.8.0 + + // protocol buffers replace + github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 +) diff --git a/go.sum b/go.sum index 5cb85ae6a99..47e6fc9bce4 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,9 @@ -bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= 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= @@ -16,17 +16,7 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY 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.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.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= 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= @@ -35,8 +25,6 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 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/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= 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= @@ -46,6 +34,7 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo 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= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.0.0-beta.2 h1:/BZRNzm8N4K4eWfK28dL4yescorxtO7YG1yun8fy+pI= filippo.io/edwards25519 v1.0.0-beta.2/go.mod h1:X+pm78QAUPtFLi1z9PYIlS/bdDnvbCOGKtZ+ACWEf7o= @@ -53,13 +42,8 @@ github.com/99designs/keyring v1.1.6 h1:kVDC2uCgVwecxCk+9zoCt2uEL6dt+dfVzMvGgnVcI github.com/99designs/keyring v1.1.6/go.mod h1:16e0ds7LGQQcT59QqkTg72Hh5ShM51Byv5PEmW6uoRU= github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= -github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= github.com/Azure/azure-storage-blob-go v0.7.0/go.mod h1:f9YQKtsG1nMisotuTPpO0tjNuEjKRYAcJU8/ydDI++4= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= @@ -71,24 +55,16 @@ github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= -github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= -github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= -github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= -github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= 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 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= @@ -97,16 +73,12 @@ github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrU github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= 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/Workiva/go-datastructures v1.0.52/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA= github.com/Workiva/go-datastructures v1.0.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSaq37xZZh7Yig= github.com/Workiva/go-datastructures v1.0.53/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A= github.com/Zilliqa/gozilliqa-sdk v1.2.1-0.20201201074141-dd0ecada1be6/go.mod h1:eSYp2T6f0apnuW8TzhV3f6Aff2SE8Dwio++U4ha4yEM= -github.com/adlio/schema v1.1.13/go.mod h1:L5Z7tw+7lRK1Fnpi/LT/ooCP1elkXn0krMWBQHUhEDE= -github.com/adlio/schema v1.3.0 h1:eSVYLxYWbm/6ReZBCkLw4Fz7uqC+ZNoPvA39bOwi52A= -github.com/adlio/schema v1.3.0/go.mod h1:51QzxkpeFs6lRY11kPye26IaFPOV+HqEj01t5aXXKfs= +github.com/adlio/schema v1.3.3 h1:oBJn8I02PyTB466pZO1UZEn1TV5XLlifBSyMrmHl/1I= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= 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= @@ -120,43 +92,34 @@ github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1: github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= -github.com/armon/go-metrics v0.3.10 h1:FR+drcQStOe+32sYyJYyZ7FIdgoGGBnwLl+flodp8Uo= -github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.4.0 h1:yCQqn7dwca4ITXb+CbubHmedzaQYHhNhrEXLYUeEe8Q= +github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-radix v1.0.0/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.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= -github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= -github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -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/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d/go.mod h1:d3C0AkH6BRcvO8T0UEPu53cnw4IbV63x1bEjildYhO0= github.com/btcsuite/btcd v0.0.0-20190315201642-aa6e0f35703c/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94= -github.com/btcsuite/btcd v0.22.0-beta h1:LTDpDKUM5EeOFBPM8IXpinEcmZ6FWfNZbE3lfrfdnWo= -github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= +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/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= 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/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= @@ -165,81 +128,60 @@ github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= 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/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/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= 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.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= 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/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= 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/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod h1:1MxXX1Ux4x6mqPmjkUgTP1CdXIBXKX7T+Jk9Gxrmx+U= 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/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-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coinbase/rosetta-sdk-go v0.7.0 h1:lmTO/JEpCvZgpbkOITL95rA80CPKb5CtMzLaqF2mCNg= github.com/coinbase/rosetta-sdk-go v0.7.0/go.mod h1:7nD3oBPIiHqhRprqvMgPoGxe/nyq3yftRmpsy29coWE= -github.com/confio/ics23/go v0.6.6/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= -github.com/confio/ics23/go v0.7.0 h1:00d2kukk7sPoHWL4zZBZwzxnpA2pec1NPdwbSokJ5w8= -github.com/confio/ics23/go v0.7.0/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= -github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= -github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.2.1 h1:/EeEo2EtN3umhbbgCveyjifoMYg0pS+nMMEemaYw634= -github.com/containerd/continuity v0.2.1/go.mod h1:wCYX+dRqZdImhGucXOqTQn05AhX6EUDaGEMUzTFFpLg= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-semver v0.3.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 v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cosmos/btcutil v1.0.4 h1:n7C2ngKXo7UC9gNyMNLbzqz7Asuf+7Qv4gnX/rOdQ44= github.com/cosmos/btcutil v1.0.4/go.mod h1:Ffqc8Hn6TJUdDgHBwIZLtrLQC1KdJ9jGJl/TvgUaxbU= -github.com/cosmos/cosmos-sdk v0.45.4 h1:eStDAhJdMY8n5arbBRe+OwpNeBSunxSBHp1g55ulfdA= -github.com/cosmos/cosmos-sdk v0.45.4/go.mod h1:WOqtDxN3eCCmnYLVla10xG7lEXkFjpTaqm2a2WasgCc= +github.com/cosmos/cosmos-sdk v0.45.10 h1:YRf1N6C7OFCc8FJ5wuhcnDDySJNDn5DxSscVgbeXgz4= +github.com/cosmos/cosmos-sdk v0.45.10/go.mod h1:CbfWNs4PuxxsvRD/snQuSBDwIhtsD7rIDTVQyYMKTa0= +github.com/cosmos/cosmos-sdk/ics23/go v0.8.0 h1:iKclrn3YEOwk4jQHT2ulgzuXyxmzmPczUalMwW4XH9k= +github.com/cosmos/cosmos-sdk/ics23/go v0.8.0/go.mod h1:2a4dBq88TUoqoWAU5eu0lGvpFP3wWDPgdHPargtyw30= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= 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/iavl v0.17.3 h1:s2N819a2olOmiauVa0WAhoIJq9EhSXE9HDBAoR9k+8Y= -github.com/cosmos/iavl v0.17.3/go.mod h1:prJoErZFABYZGDHka1R6Oay4z9PrNeFFiMKHDAMOi4w= +github.com/cosmos/gorocksdb v1.2.0 h1:d0l3jJG8M4hBouIZq0mDUHZ+zjOx044J3nGRskwTb4Y= +github.com/cosmos/gorocksdb v1.2.0/go.mod h1:aaKvKItm514hKfNJpUJXnnOWeBnk2GL4+Qw9NHizILw= +github.com/cosmos/iavl v0.19.3 h1:cESO0OwTTxQm5rmyESKW+zESheDUYI7CcZDWWDwnuxg= +github.com/cosmos/iavl v0.19.3/go.mod h1:X9PKD3J0iFxdmgNLa7b2LYWdsGd90ToV5cAONApkEPw= github.com/cosmos/ledger-cosmos-go v0.11.1 h1:9JIYsGnXP613pb2vPjFeMMjBI5lEDsEaF6oYorTy6J4= github.com/cosmos/ledger-cosmos-go v0.11.1/go.mod h1:J8//BsAGTo3OC/vDLjMRFLW6q0WAaXvHnVc7ZmE8iUY= github.com/cosmos/ledger-go v0.9.2 h1:Nnao/dLwaVTk1Q5U9THldpUMMXU94BOTWPddSmVB6pI= github.com/cosmos/ledger-go v0.9.2/go.mod h1:oZJ2hHAZROdlHiwTg4t7kP+GKIIkBT+o6c9QWFanOyI= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 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.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +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/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/danieljoos/wincred v1.0.2 h1:zf4bhty2iLuwgjgpraD2E9UbvO+fe54XXGJbOwe23fU= github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -248,7 +190,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= -github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= github.com/dgraph-io/badger/v2 v2.2007.2 h1:EjjK0KqwaFMlPin1ajhP943VPENHJdEz1KLIegjaI3k= @@ -262,12 +203,9 @@ github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WA github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= @@ -286,46 +224,31 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m 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.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= github.com/ethereum/go-ethereum v1.9.25/go.mod h1:vMkFiYLHI4tgPw4k2j4MHKoovchFE8plZ0M9VMk4/oM= -github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ= -github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= +github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= -github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= -github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y= -github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= +github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= -github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= -github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/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/goblin v0.0.0-20210519012713-85d372ac71e2/go.mod h1:VzmDKDJVZI3aJmnRI9VjAn9nJ8qPPsN1fqzr9dqInIo= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= -github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= 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.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= -github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= -github.com/gin-gonic/gin v1.7.0 h1:jGB9xAJQ12AIGNB4HguylppmDK1Am9ppF7XnGXXJuoU= -github.com/gin-gonic/gin v1.7.0/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= 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= @@ -335,8 +258,9 @@ github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgO github.com/go-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4= github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-kit/log v0.2.0 h1:7i2K3eKTos3Vc0enKCfnVcgHh2olr/MyfboYq7cAcFw= github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +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-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= @@ -353,10 +277,7 @@ github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7a github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 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-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= @@ -369,17 +290,11 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x github.com/gogo/gateway v1.1.0 h1:u0SuhL9+Il+UbjM9VIE3ntfRujKbvVpFvNB4HbjeVQ0= github.com/gogo/gateway v1.1.0/go.mod h1:S7rR8FRQyG3QFESeSv4l2WnsyzlCLG0CzBbUUo/mbic= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= -github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= -github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188/go.mod h1:vXjM/+wXQnTPR4KqTKDgJukSZ6amVRtWMPEjE6sQoK8= -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/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/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/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= @@ -387,9 +302,7 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt 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= @@ -406,7 +319,6 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD 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 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -425,11 +337,9 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ 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 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= 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.1.1-0.20200604201612-c04b05f3adfa h1:Q75Upo5UN4JbPFURXZ8nLKYUvF85dyFRop/vQ0Rv+64= @@ -437,7 +347,6 @@ github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6 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/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= @@ -449,21 +358,15 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf 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-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-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/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/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= @@ -472,22 +375,17 @@ github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 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.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 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= @@ -498,29 +396,18 @@ 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.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= -github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= -github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= 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/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= 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-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-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= 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-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= 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= @@ -536,41 +423,30 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 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/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= -github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= -github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/hdevalence/ed25519consensus v0.0.0-20210204194344-59a8610d2b87 h1:uUjLpLt6bVvZ72SQc/B4dXcPBw4Vgd7soowdRl52qEM= github.com/hdevalence/ed25519consensus v0.0.0-20210204194344-59a8610d2b87/go.mod h1:XGsKKeXxeRr95aEOgipvluMPlgjr7dGlk9ZTWOjcUcg= github.com/holiman/uint256 v1.1.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= -github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= -github.com/iancoleman/strcase v0.2.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.14.1 h1:NrN4PY71A6tAz2sKDvC5JCauENWp0ykG8Oq1H3cpFvw= github.com/improbable-eng/grpc-web v0.14.1/go.mod h1:zEjGHa8DAlkoOXmswrNvhUGEYQA9UI7DhrGeHR1DMGU= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jhump/protoreflect v1.9.0 h1:npqHz788dryJiR/l6K/RUQAyh2SwV91+d1dnh4RjO9w= -github.com/jhump/protoreflect v1.9.0/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg= +github.com/jhump/protoreflect v1.12.1-0.20220721211354-060cc04fc18b h1:izTof8BKh/nE1wrKOrloNA5q4odOarjf+Xpe+4qow98= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -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= @@ -590,7 +466,6 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 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.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d h1:Z+RDyXzjKE0i2sTjZ/b1uxiGtPhFy34Ou/Tk0qwN0kM= github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc= @@ -599,72 +474,60 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= 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.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= 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.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 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/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= -github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs= -github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= +github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= +github.com/lib/pq v1.10.6/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/lucasjones/reggen v0.0.0-20180717132126-cdb49ff09d77/go.mod h1:5ELEyG+X8f+meRWHuqUOewBOhvHkl7M76pdGEansxW4= -github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= -github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -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 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= 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.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/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.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= -github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= -github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= -github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 h1:hLDRPB66XQT/8+wG9WsDpiCvZf1yKO7sz7scAjSlBa0= github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM= -github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= +github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 h1:QRUSJEgZn2Snx0EmT/QLXibWjSUDjKWvXIT19NBVp94= +github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= 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= @@ -674,10 +537,8 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu 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.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= -github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +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/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= @@ -685,8 +546,6 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN 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/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= -github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= 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/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -697,24 +556,14 @@ github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= 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/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q= -github.com/nats-io/jwt/v2 v2.0.3/go.mod h1:VRP+deawSXyhNjXmxPCHskrR6Mq50BqpEI5SEcNiGlY= github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= -github.com/nats-io/nats-server/v2 v2.5.0/go.mod h1:Kj86UtrXAL6LwYRA6H4RqzkHhK0Vcv2ZnKD5WbQ1t3g= github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= -github.com/nats-io/nats.go v1.12.1/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= 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/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s= -github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/neilotoole/errgroup v0.1.5/go.mod h1:Q2nLGf+594h0CLBs/Mbg6qOr7GtqDK7C2S41udRnToE= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 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/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= @@ -724,47 +573,27 @@ github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c/go.mod h1 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 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= -github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= 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 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak= -github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= -github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= 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.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= -github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= -github.com/opencontainers/runc v1.0.3 h1:1hbqejyQWCJBvtKAfdO0b1FmaEf2z/bxnjqbARass5k= -github.com/opencontainers/runc v1.0.3/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= -github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= +github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= +github.com/opencontainers/runc v1.1.3 h1:vIXrkId+0/J2Ymu2m7VjGvbSlAId9XNRPhn2p4b+d8w= 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/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 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/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= 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/otiai10/copy v1.6.0 h1:IinKAryFFuPONZ7cm6T6E2QX/vcJwSnlaA5lfoaXIiQ= -github.com/otiai10/copy v1.6.0/go.mod h1:XWfuS3CrI0R6IE0FbgHsEazaXO8G0LpMp9o8tos0x4E= -github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= -github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= -github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= -github.com/otiai10/mint v1.3.2/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= 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= @@ -772,38 +601,36 @@ github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144T github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= -github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +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.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= +github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= -github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= 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/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= 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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= 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 v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 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.8.0/go.mod h1:O9VU6huf47PktckDQfMTX0Y8tY0/7TSWwj+ITvv0TnM= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34= +github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= 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= @@ -813,35 +640,30 @@ github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2 github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.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.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= 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.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.34.0 h1:RBmGO9d/FVjqHT0yUGQwBJhkwKV+wPCn7KGpvfab0uE= +github.com/prometheus/common v0.34.0/go.mod h1:gB3sOl7P0TvJabZpLY5uQMpUqRCPPCyRLCZYc7JZTNE= 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.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 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.2.0/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.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= -github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/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/cosmos-proto v0.3.1 h1:rV7iM4SSFAagvy8RiyhiACbWEGotmqzywPxOvwMdxcg= github.com/regen-network/cosmos-proto v0.3.1/go.mod h1:jO0sVX6a1B36nmE8C9xBFXpNwWejXC7QqCOnH3O0+YM= github.com/regen-network/protobuf v1.3.3-alpha.regen.1 h1:OHEc+q5iIAXpqiqFKeLpu5NwTIkVXUs48vFMwzqpqY4= @@ -850,55 +672,48 @@ github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRr 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 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/zerolog v1.23.0 h1:UskrK+saS9P9Y789yNNulYKdARjPZuS35B8gJF2x60g= -github.com/rs/zerolog v1.23.0/go.mod h1:6c7hFfxPOy7TacJc4Fcdi24/J0NKYGzjG8FWRI916Qo= +github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs= +github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 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/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= -github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa h1:0U2s5loxrTy6/VgfVoLuVLFJcURKLH49ie0zSch7gh4= -github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= +github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0= +github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= github.com/shirou/gopsutil v2.20.5+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.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 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.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 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/snikch/goodman v0.0.0-20171125024755-10e37e294daa/go.mod h1:oJyF+mSPHbB5mVY2iO9KV3pTt/QbIkGaO8gQ2WrDbP4= 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/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= -github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= -github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= +github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= -github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= -github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= -github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= +github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= +github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -907,63 +722,52 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk= -github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU= +github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU= +github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw= github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU= 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/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= -github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e/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.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 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 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= +github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca h1:Ld/zXl5t4+D69SiV4JoN7kkfvJdOWlPpfxrzxpLMoUk= github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= -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/btcd v0.1.1 h1:0VcxPfflS2zZ3RiOAHkBiFUcPvbtRj5O7zHmcJWHV7s= github.com/tendermint/btcd v0.1.1/go.mod h1:DC6/m53jtQzr/NFmMNEu0rxf18/ktVoVtMrnDD5pN+U= github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 h1:hqAk8riJvK4RMWx1aInLzndwxKalgi5rTqgfXxOxbEI= github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15/go.mod h1:z4YtwM70uOnk8h0pjJYlj3zdYwi9l03By6iAIF5j/Pk= 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.34.14/go.mod h1:FrwVm3TvsVicI9Z7FlucHV6Znfd5KBc/Lpp69cCwtk0= -github.com/tendermint/tendermint v0.34.19 h1:y0P1qI5wSa9IRuhKnTDA6IUcOrLi1hXJuALR+R7HFEk= -github.com/tendermint/tendermint v0.34.19/go.mod h1:R5+wgIwSxMdKQcmOaeudL0Cjkr3HDkhpcdum6VeU3R4= -github.com/tendermint/tm-db v0.6.4/go.mod h1:dptYhIpJ2M5kUuenLr+Yyf3zQOv1SgBZcl8/BmWlMBw= -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/tendermint/tendermint v0.34.22 h1:XMhtC8s8QqJO4l/dn+TkQvevTRSow3Vixjclr41o+2Q= +github.com/tendermint/tendermint v0.34.22/go.mod h1:YpP5vBEAKUT4g6oyfjKgFeZmdB/GjkJAxfF+cgmJg6Y= +github.com/tendermint/tm-db v0.6.7 h1:fE00Cbl0jayAoqlExN6oyQJ7fR/ZtoVOmvPJ//+shu8= +github.com/tendermint/tm-db v0.6.7/go.mod h1:byQDzFkZV1syXr/ReXS808NxA2xvyuuVgXOJ/088L6I= github.com/tidwall/gjson v1.6.7/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/sjson v1.1.4/go.mod h1:wXpKXu8CtDjKAZ+3DrKY5ROCorDFahq8l0tey/Lx1fg= github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -971,8 +775,6 @@ github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 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/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vmihailenco/msgpack/v5 v5.1.4/go.mod h1:C5gboKD0TJPqWDTVTtrQNfRbiBwHZGo8UTqP/9/XvLI= github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= @@ -983,22 +785,12 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de 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/zondax/hid v0.9.0 h1:eiT3P6vNxAEVxXMw66eZUAAnU2zD33JBkfG/EnfAKl8= github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= -go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= -go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= -go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= 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= @@ -1007,23 +799,14 @@ 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.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= 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/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 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.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= 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.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 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= @@ -1033,28 +816,19 @@ golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8U 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-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210915214749-c084706c2272 h1:3erb+vDS8lU1sxfDHF4/hhWyaXnhIaO+7RgL4fDZORA= -golang.org/x/crypto v0.0.0-20210915214749-c084706c2272/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -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/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 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-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= @@ -1066,7 +840,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 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/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= +golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= 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/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1080,7 +855,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu 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/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= @@ -1093,8 +867,6 @@ 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/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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= @@ -1111,14 +883,12 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn 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-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 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-20190923162816-aa69164e4478/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= @@ -1139,24 +909,15 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200904194848-62affa334b73/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-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211208012354-db4efeb81f4b h1:MWaHNqZy3KTpuTMAGvv+Kw+ylsEpmyJZizz1dqxnu28= -golang.org/x/net v0.0.0-20211208012354-db4efeb81f4b/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-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E= +golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 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= @@ -1166,14 +927,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ 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-20211005180243-6b3c2da341f1/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/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= @@ -1184,7 +939,6 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ 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/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= @@ -1196,37 +950,29 @@ golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190130150945-aca44879d564/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-20190222072716-a9d3bda3a223/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-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/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-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/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-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191115151921-52ab43148777/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-20191210023423-ac6580df4449/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-20200124204421-9fbb57f87de9/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= @@ -1248,45 +994,30 @@ golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/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-20210303074136-134d130e1a04/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-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/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-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-20210903071746-97244b99971b/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-20210917161153-d61c044b1678/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-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/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-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/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-20220818161305-2296e01440c6 h1:Sx/u41w+OwrInGdEckYmEuU5gHoGSL4QbDz3S9s6j4U= +golang.org/x/sys v0.0.0-20220818161305-2296e01440c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc= +golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 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= @@ -1301,13 +1032,9 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb 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-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -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= @@ -1320,12 +1047,10 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw 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-20190907020128-2ca718005c18/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-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-20191112195655-aa38f8e97acc/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= @@ -1349,10 +1074,8 @@ golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWc 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-20200522201501-cb1345f3a375/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-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 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= @@ -1361,24 +1084,15 @@ golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/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/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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -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/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= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618= 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= @@ -1399,19 +1113,6 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 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.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= -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/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= @@ -1456,40 +1157,14 @@ google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6D 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-20201119123407-9b1e624d6bc4/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-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-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-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/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-20211223182754-3ac035c7e7cb h1:ZrsicilzPCS/Xr8qtBZZLpy4P9TYXAfl49ctG1/5tgw= -google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20220725144611-272f38e5d71b h1:SfSkJugek6xm7lWywqth4r2iTrYLpD8lOj1nMIIhMNM= +google.golang.org/genproto v0.0.0-20220725144611-272f38e5d71b/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= 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.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -1514,20 +1189,8 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp 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.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/grpc v1.50.0 h1:fPVVDxY9w++VjTZsYvXWqEf9Rqar/e+9zYfxKK+W+YU= +google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= 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= @@ -1538,26 +1201,21 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 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.25.1-0.20200805231151-a709e31e5d12/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 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= -gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/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/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= -gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= @@ -1576,8 +1234,9 @@ 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 h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 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 v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= 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= @@ -1590,9 +1249,7 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k= nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= 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.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/modules/apps/27-interchain-accounts/controller/client/cli/cli.go b/modules/apps/27-interchain-accounts/controller/client/cli/cli.go index 0d2f54bd59b..7aa86cc0e46 100644 --- a/modules/apps/27-interchain-accounts/controller/client/cli/cli.go +++ b/modules/apps/27-interchain-accounts/controller/client/cli/cli.go @@ -14,6 +14,7 @@ func GetQueryCmd() *cobra.Command { } queryCmd.AddCommand( + GetCmdQueryInterchainAccount(), GetCmdParams(), ) diff --git a/modules/apps/27-interchain-accounts/controller/client/cli/query.go b/modules/apps/27-interchain-accounts/controller/client/cli/query.go index 9a939906b90..f9452e2aac7 100644 --- a/modules/apps/27-interchain-accounts/controller/client/cli/query.go +++ b/modules/apps/27-interchain-accounts/controller/client/cli/query.go @@ -11,6 +11,40 @@ import ( "github.com/cosmos/ibc-go/v3/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{ diff --git a/modules/apps/27-interchain-accounts/controller/ibc_module_test.go b/modules/apps/27-interchain-accounts/controller/ibc_module_test.go index db4412d144e..6c1a5a67cf3 100644 --- a/modules/apps/27-interchain-accounts/controller/ibc_module_test.go +++ b/modules/apps/27-interchain-accounts/controller/ibc_module_test.go @@ -7,7 +7,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" "github.com/stretchr/testify/suite" - "github.com/tendermint/tendermint/crypto" "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/controller/types" icatypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/types" @@ -18,12 +17,6 @@ import ( ) var ( - // TODO: Cosmos-SDK ADR-28: Update crypto.AddressHash() when sdk uses address.Module() - // https://github.com/cosmos/cosmos-sdk/issues/10225 - // - // TestAccAddress defines a resuable bech32 address for testing purposes - TestAccAddress = icatypes.GenerateAddress(sdk.AccAddress(crypto.AddressHash([]byte(icatypes.ModuleName))), ibctesting.FirstConnectionID, TestPortID) - // TestOwnerAddress defines a reusable bech32 address for testing purposes TestOwnerAddress = "cosmos17dtl0mjt3t77kpuhg2edqzjpszulwhgzuj9ljs" @@ -120,9 +113,7 @@ func SetupICAPath(path *ibctesting.Path, owner string) error { } func (suite *InterchainAccountsTestSuite) TestOnChanOpenInit() { - var ( - channel *channeltypes.Channel - ) + var channel *channeltypes.Channel testCases := []struct { name string @@ -260,9 +251,7 @@ func (suite *InterchainAccountsTestSuite) TestChanOpenTry() { } func (suite *InterchainAccountsTestSuite) TestOnChanOpenAck() { - var ( - path *ibctesting.Path - ) + var path *ibctesting.Path testCases := []struct { name string @@ -323,10 +312,8 @@ func (suite *InterchainAccountsTestSuite) TestOnChanOpenAck() { } else { suite.Require().Error(err) } - }) } - } // Test initiating a ChanOpenConfirm using the controller chain instead of the host chain @@ -377,7 +364,6 @@ func (suite *InterchainAccountsTestSuite) TestChanOpenConfirm() { suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, ) suite.Require().Error(err) - } // OnChanCloseInit on controller (chainA) @@ -402,16 +388,13 @@ func (suite *InterchainAccountsTestSuite) TestOnChanCloseInit() { } func (suite *InterchainAccountsTestSuite) TestOnChanCloseConfirm() { - var ( - path *ibctesting.Path - ) + var path *ibctesting.Path testCases := []struct { name string malleate func() expPass bool }{ - { "success", func() {}, true, }, @@ -442,13 +425,11 @@ func (suite *InterchainAccountsTestSuite) TestOnChanCloseConfirm() { } else { suite.Require().Error(err) } - }) } } func (suite *InterchainAccountsTestSuite) TestOnRecvPacket() { - testCases := []struct { name string malleate func() @@ -490,16 +471,14 @@ func (suite *InterchainAccountsTestSuite) TestOnRecvPacket() { 0, ) - ack := cbs.OnRecvPacket(suite.chainA.GetContext(), packet, TestAccAddress) + ack := cbs.OnRecvPacket(suite.chainA.GetContext(), packet, nil) suite.Require().Equal(tc.expPass, ack.Success()) }) } } func (suite *InterchainAccountsTestSuite) TestOnAcknowledgementPacket() { - var ( - path *ibctesting.Path - ) + var path *ibctesting.Path testCases := []struct { msg string @@ -568,9 +547,7 @@ func (suite *InterchainAccountsTestSuite) TestOnAcknowledgementPacket() { } func (suite *InterchainAccountsTestSuite) TestOnTimeoutPacket() { - var ( - path *ibctesting.Path - ) + var path *ibctesting.Path testCases := []struct { msg string diff --git a/modules/apps/27-interchain-accounts/controller/keeper/account.go b/modules/apps/27-interchain-accounts/controller/keeper/account.go index 03eeef69f1f..5087ac5a9da 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/account.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/account.go @@ -3,6 +3,7 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" icatypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/types" channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" @@ -10,7 +11,7 @@ import ( ) // RegisterInterchainAccount is the entry point to registering an interchain account. -// It generates a new port identifier using the owner address. It will bind to the +// It generates a new port identifier using the owner address. It will bind to the // port identifier and call 04-channel 'ChanOpenInit'. 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 MsgChanOpenInit must be used. @@ -56,7 +57,7 @@ func (k Keeper) RegisterInterchainAccount(ctx sdk.Context, connectionID, owner s return err } - msg := channeltypes.NewMsgChannelOpenInit(portID, string(versionBytes), channeltypes.ORDERED, []string{connectionID}, icatypes.PortID, icatypes.ModuleName) + msg := channeltypes.NewMsgChannelOpenInit(portID, string(versionBytes), channeltypes.ORDERED, []string{connectionID}, icatypes.PortID, authtypes.NewModuleAddress(icatypes.ModuleName).String()) handler := k.msgRouter.Handler(msg) res, err := handler(ctx, msg) diff --git a/modules/apps/27-interchain-accounts/controller/keeper/genesis_test.go b/modules/apps/27-interchain-accounts/controller/keeper/genesis_test.go index 7a41608d38f..83878761631 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/genesis_test.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/genesis_test.go @@ -10,6 +10,7 @@ import ( func (suite *KeeperTestSuite) TestInitGenesis() { suite.SetupTest() + interchainAccAddr := icatypes.GenerateUniqueAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, TestPortID) genesisState := icatypes.ControllerGenesisState{ ActiveChannels: []icatypes.ActiveChannel{ { @@ -22,7 +23,7 @@ func (suite *KeeperTestSuite) TestInitGenesis() { { ConnectionId: ibctesting.FirstConnectionID, PortId: TestPortID, - AccountAddress: TestAccAddress.String(), + AccountAddress: interchainAccAddr.String(), }, }, Ports: []string{TestPortID}, @@ -36,12 +37,11 @@ func (suite *KeeperTestSuite) TestInitGenesis() { accountAdrr, found := suite.chainA.GetSimApp().ICAControllerKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), ibctesting.FirstConnectionID, TestPortID) suite.Require().True(found) - suite.Require().Equal(TestAccAddress.String(), accountAdrr) + suite.Require().Equal(interchainAccAddr.String(), accountAdrr) expParams := types.NewParams(false) params := suite.chainA.GetSimApp().ICAControllerKeeper.GetParams(suite.chainA.GetContext()) suite.Require().Equal(expParams, params) - } func (suite *KeeperTestSuite) TestExportGenesis() { @@ -53,12 +53,15 @@ func (suite *KeeperTestSuite) TestExportGenesis() { 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().Equal(TestAccAddress.String(), genesisState.InterchainAccounts[0].AccountAddress) + 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()) diff --git a/modules/apps/27-interchain-accounts/controller/keeper/grpc_query.go b/modules/apps/27-interchain-accounts/controller/keeper/grpc_query.go index dd1d04a96b3..661f6ce6bfb 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/grpc_query.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/grpc_query.go @@ -4,16 +4,41 @@ import ( "context" sdk "github.com/cosmos/cosmos-sdk/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/controller/types" + icatypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/types" ) var _ types.QueryServer = Keeper{} +// 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") + } + + portID, err := icatypes.NewControllerPortID(req.Owner) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "failed to generate portID from owner address: %s", err) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + 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 (q Keeper) Params(c context.Context, _ *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { +func (k Keeper) Params(c context.Context, _ *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { ctx := sdk.UnwrapSDKContext(c) - params := q.GetParams(ctx) + params := k.GetParams(ctx) return &types.QueryParamsResponse{ Params: ¶ms, 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 index e76cdac1fb4..3ba70e735db 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/grpc_query_test.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/grpc_query_test.go @@ -4,8 +4,77 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/controller/types" + ibctesting "github.com/cosmos/ibc-go/v3/testing" ) +func (suite *KeeperTestSuite) TestQueryInterchainAccount() { + var req *types.QueryInterchainAccountRequest + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "success", + func() {}, + true, + }, + { + "empty request", + func() { + req = nil + }, + false, + }, + { + "empty owner address", + func() { + req.Owner = "" + }, + false, + }, + { + "invalid connection, account address not found", + func() { + req.ConnectionId = "invalid-connection-id" + }, + false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + path := NewICAPath(suite.chainA, suite.chainB) + suite.coordinator.SetupConnections(path) + + 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(sdk.WrapSDKContext(suite.chainA.GetContext()), req) + + if tc.expPass { + 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().Error(err) + } + }) + } +} + func (suite *KeeperTestSuite) TestQueryParams() { ctx := sdk.WrapSDKContext(suite.chainA.GetContext()) expParams := types.DefaultParams() diff --git a/modules/apps/27-interchain-accounts/controller/keeper/handshake.go b/modules/apps/27-interchain-accounts/controller/keeper/handshake.go index 26a90878abf..6b84fb554ca 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/handshake.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/handshake.go @@ -119,6 +119,5 @@ func (k Keeper) OnChanCloseConfirm( portID, channelID string, ) error { - 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 index 61c8a092a81..eec74a49e81 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/handshake_test.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/handshake_test.go @@ -22,12 +22,9 @@ func (suite *KeeperTestSuite) TestOnChanOpenInit() { malleate func() expPass bool }{ - { "success", - func() { - path.EndpointA.SetChannel(*channel) - }, + func() {}, true, }, { @@ -48,30 +45,44 @@ func (suite *KeeperTestSuite) TestOnChanOpenInit() { }, true, }, + { + "success: channel reopening", + func() { + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + err = path.EndpointA.SetChannelClosed() + suite.Require().NoError(err) + + err = path.EndpointB.SetChannelClosed() + suite.Require().NoError(err) + + path.EndpointA.ChannelID = "" + path.EndpointB.ChannelID = "" + }, + true, + }, { "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: channeltypes.ORDERED, Counterparty: counterparty, ConnectionHops: []string{path.EndpointA.ConnectionID}, - Version: TestVersion, + Version: string(versionBytes), } - path.EndpointA.SetChannel(closedChannel) - - // modify metadata - metadata.Version = "ics27-2" - - versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) - suite.Require().NoError(err) - - channel.Version = string(versionBytes) }, false, }, @@ -242,7 +253,6 @@ func (suite *KeeperTestSuite) TestOnChanOpenInit() { } else { suite.Require().Error(err) } - }) } } @@ -370,7 +380,10 @@ func (suite *KeeperTestSuite) TestOnChanOpenAck() { err = path.EndpointB.ChanOpenTry() suite.Require().NoError(err) - metadata = icatypes.NewMetadata(icatypes.Version, ibctesting.FirstConnectionID, ibctesting.FirstConnectionID, TestAccAddress.String(), icatypes.EncodingProtobuf, icatypes.TxTypeSDKMultiMsg) + 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) @@ -402,16 +415,13 @@ func (suite *KeeperTestSuite) TestOnChanOpenAck() { } func (suite *KeeperTestSuite) TestOnChanCloseConfirm() { - var ( - path *ibctesting.Path - ) + var path *ibctesting.Path testCases := []struct { name string malleate func() expPass bool }{ - { "success", func() {}, true, }, @@ -441,7 +451,6 @@ func (suite *KeeperTestSuite) TestOnChanCloseConfirm() { } 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 index 87af9ae9c6f..a97a93689cd 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/keeper.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/keeper.go @@ -39,7 +39,6 @@ func NewKeeper( ics4Wrapper icatypes.ICS4Wrapper, channelKeeper icatypes.ChannelKeeper, portKeeper icatypes.PortKeeper, scopedKeeper capabilitykeeper.ScopedKeeper, msgRouter *baseapp.MsgServiceRouter, ) Keeper { - // set KeyTable if it has not already been set if !paramSpace.HasKeyTable() { paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable()) diff --git a/modules/apps/27-interchain-accounts/controller/keeper/keeper_test.go b/modules/apps/27-interchain-accounts/controller/keeper/keeper_test.go index c3e1a48ee0c..eb1ff451899 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/keeper_test.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/keeper_test.go @@ -3,9 +3,7 @@ package keeper_test import ( "testing" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/suite" - "github.com/tendermint/tendermint/crypto" icatypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/types" channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" @@ -13,12 +11,6 @@ import ( ) var ( - // TODO: Cosmos-SDK ADR-28: Update crypto.AddressHash() when sdk uses address.Module() - // https://github.com/cosmos/cosmos-sdk/issues/10225 - // - // TestAccAddress defines a resuable bech32 address for testing purposes - TestAccAddress = icatypes.GenerateAddress(sdk.AccAddress(crypto.AddressHash([]byte(icatypes.ModuleName))), ibctesting.FirstConnectionID, TestPortID) - // TestOwnerAddress defines a reusable bech32 address for testing purposes TestOwnerAddress = "cosmos17dtl0mjt3t77kpuhg2edqzjpszulwhgzuj9ljs" @@ -153,11 +145,10 @@ func (suite *KeeperTestSuite) TestGetInterchainAccountAddress() { suite.Require().NoError(err) counterpartyPortID := path.EndpointA.ChannelConfig.PortID - expectedAddr := icatypes.GenerateAddress(suite.chainA.GetSimApp().AccountKeeper.GetModuleAddress(icatypes.ModuleName), ibctesting.FirstConnectionID, counterpartyPortID) retrievedAddr, found := suite.chainA.GetSimApp().ICAControllerKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), ibctesting.FirstConnectionID, counterpartyPortID) suite.Require().True(found) - suite.Require().Equal(expectedAddr.String(), retrievedAddr) + suite.Require().NotEmpty(retrievedAddr) retrievedAddr, found = suite.chainA.GetSimApp().ICAControllerKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), "invalid conn", "invalid port") suite.Require().False(found) @@ -212,13 +203,16 @@ func (suite *KeeperTestSuite) TestGetAllInterchainAccounts() { 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 := []icatypes.RegisteredInterchainAccount{ { ConnectionId: ibctesting.FirstConnectionID, PortId: TestPortID, - AccountAddress: TestAccAddress.String(), + AccountAddress: interchainAccAddr, }, { ConnectionId: ibctesting.FirstConnectionID, diff --git a/modules/apps/27-interchain-accounts/controller/keeper/relay_test.go b/modules/apps/27-interchain-accounts/controller/keeper/relay_test.go index bb4d17b8232..2a1f9894087 100644 --- a/modules/apps/27-interchain-accounts/controller/keeper/relay_test.go +++ b/modules/apps/27-interchain-accounts/controller/keeper/relay_test.go @@ -183,9 +183,7 @@ func (suite *KeeperTestSuite) TestSendTx() { } func (suite *KeeperTestSuite) TestOnTimeoutPacket() { - var ( - path *ibctesting.Path - ) + var path *ibctesting.Path testCases := []struct { msg string diff --git a/modules/apps/27-interchain-accounts/controller/types/params.go b/modules/apps/27-interchain-accounts/controller/types/params.go index eb9c413bec0..d7b9cc83874 100644 --- a/modules/apps/27-interchain-accounts/controller/types/params.go +++ b/modules/apps/27-interchain-accounts/controller/types/params.go @@ -11,10 +11,8 @@ const ( DefaultControllerEnabled = true ) -var ( - // KeyControllerEnabled is the store key for ControllerEnabled Params - KeyControllerEnabled = []byte("ControllerEnabled") -) +// KeyControllerEnabled is the store key for ControllerEnabled Params +var KeyControllerEnabled = []byte("ControllerEnabled") // ParamKeyTable type declaration for parameters func ParamKeyTable() paramtypes.KeyTable { diff --git a/modules/apps/27-interchain-accounts/controller/types/query.pb.go b/modules/apps/27-interchain-accounts/controller/types/query.pb.go index dbbdfeec611..852995d1985 100644 --- a/modules/apps/27-interchain-accounts/controller/types/query.pb.go +++ b/modules/apps/27-interchain-accounts/controller/types/query.pb.go @@ -6,6 +6,7 @@ package types import ( context "context" fmt "fmt" + _ "github.com/gogo/protobuf/gogoproto" grpc1 "github.com/gogo/protobuf/grpc" proto "github.com/gogo/protobuf/proto" _ "google.golang.org/genproto/googleapis/api/annotations" @@ -28,6 +29,104 @@ var _ = math.Inf // 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" yaml:"connection_id"` +} + +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 { } @@ -36,7 +135,7 @@ 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{0} + return fileDescriptor_df0d8b259d72854e, []int{2} } func (m *QueryParamsRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -75,7 +174,7 @@ 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{1} + return fileDescriptor_df0d8b259d72854e, []int{3} } func (m *QueryParamsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -112,6 +211,8 @@ func (m *QueryParamsResponse) GetParams() *Params { } 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") } @@ -121,27 +222,37 @@ func init() { } var fileDescriptor_df0d8b259d72854e = []byte{ - // 315 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x91, 0x31, 0x4b, 0x33, 0x31, - 0x1c, 0xc6, 0x9b, 0x17, 0xde, 0x0e, 0x71, 0x8b, 0x0e, 0x52, 0x24, 0x48, 0x27, 0x97, 0x26, 0xf4, - 0x2a, 0x08, 0x1d, 0x1c, 0x14, 0x74, 0xad, 0x1d, 0x5d, 0x24, 0x17, 0xc3, 0x35, 0x72, 0x97, 0x7f, - 0x9a, 0xe4, 0x0a, 0x5d, 0xfd, 0x04, 0x82, 0x5f, 0xca, 0xb1, 0x20, 0x82, 0x9b, 0x72, 0xe7, 0x07, - 0x91, 0xde, 0x1d, 0xb4, 0x62, 0x07, 0xad, 0x6b, 0xfe, 0x3c, 0xbf, 0x5f, 0x1e, 0x1e, 0x7c, 0xaa, - 0x63, 0xc9, 0x85, 0xb5, 0xa9, 0x96, 0x22, 0x68, 0x30, 0x9e, 0x6b, 0x13, 0x94, 0x93, 0x13, 0xa1, - 0xcd, 0x8d, 0x90, 0x12, 0x72, 0x13, 0x3c, 0x97, 0x60, 0x82, 0x83, 0x34, 0x55, 0x8e, 0xcf, 0xfa, - 0x7c, 0x9a, 0x2b, 0x37, 0x67, 0xd6, 0x41, 0x00, 0x12, 0xe9, 0x58, 0xb2, 0xf5, 0x3c, 0xdb, 0x90, - 0x67, 0xab, 0x3c, 0x9b, 0xf5, 0x3b, 0xe7, 0x5b, 0x38, 0xd7, 0x08, 0x95, 0xb8, 0x73, 0x90, 0x00, - 0x24, 0xa9, 0xe2, 0xc2, 0x6a, 0x2e, 0x8c, 0x81, 0xd0, 0xe8, 0xab, 0x6b, 0x77, 0x0f, 0x93, 0xab, - 0xe5, 0x2f, 0x47, 0xc2, 0x89, 0xcc, 0x8f, 0xd5, 0x34, 0x57, 0x3e, 0x74, 0x35, 0xde, 0xfd, 0xf2, - 0xea, 0x2d, 0x18, 0xaf, 0xc8, 0x18, 0xb7, 0x6d, 0xf5, 0xb2, 0x8f, 0x0e, 0xd1, 0xd1, 0x4e, 0x34, - 0x64, 0xbf, 0x2f, 0xc5, 0x1a, 0x66, 0x43, 0x8a, 0xde, 0x10, 0xfe, 0x5f, 0xb9, 0xc8, 0x0b, 0xc2, - 0xed, 0xfa, 0x48, 0x2e, 0xb6, 0x01, 0x7f, 0xef, 0xd1, 0xb9, 0xfc, 0x33, 0xa7, 0x6e, 0xde, 0x1d, - 0xde, 0x3f, 0x7f, 0x3c, 0xfe, 0x3b, 0x26, 0x11, 0x6f, 0x26, 0xf9, 0xc9, 0x14, 0x75, 0xc3, 0xb3, - 0xbb, 0xa7, 0x82, 0xa2, 0x45, 0x41, 0xd1, 0x7b, 0x41, 0xd1, 0x43, 0x49, 0x5b, 0x8b, 0x92, 0xb6, - 0x5e, 0x4b, 0xda, 0xba, 0x1e, 0x25, 0x3a, 0x4c, 0xf2, 0x98, 0x49, 0xc8, 0xb8, 0x04, 0x9f, 0x81, - 0x5f, 0xe2, 0x7b, 0x09, 0xf0, 0xd9, 0x80, 0x67, 0x70, 0x9b, 0xa7, 0xca, 0xd7, 0xb2, 0xe8, 0xa4, - 0xb7, 0xf2, 0xf5, 0x36, 0xf9, 0xc2, 0xdc, 0x2a, 0x1f, 0xb7, 0xab, 0x55, 0x07, 0x9f, 0x01, 0x00, - 0x00, 0xff, 0xff, 0x49, 0xe2, 0x61, 0x29, 0xae, 0x02, 0x00, 0x00, + // 465 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x93, 0x41, 0x6b, 0x14, 0x31, + 0x14, 0xc7, 0x77, 0x56, 0xba, 0x62, 0xd4, 0x83, 0x71, 0x0f, 0xcb, 0xa2, 0xa3, 0xcc, 0xc9, 0xcb, + 0x26, 0x74, 0x2a, 0x08, 0x0b, 0x0a, 0x56, 0x50, 0x7a, 0x6b, 0xe7, 0x20, 0xe2, 0xc1, 0x92, 0xcd, + 0x84, 0x69, 0x64, 0x26, 0x6f, 0x9a, 0x64, 0x56, 0x96, 0xd2, 0x8b, 0x9f, 0x40, 0xf0, 0xe6, 0x27, + 0xf2, 0x58, 0x10, 0xc1, 0x93, 0xc8, 0xae, 0x9f, 0xc0, 0xb3, 0x07, 0x99, 0x4c, 0x74, 0x3b, 0x58, + 0xc5, 0x5d, 0x7b, 0x9a, 0xbc, 0xf7, 0x78, 0xff, 0xdf, 0x7b, 0xf9, 0x67, 0xd0, 0x03, 0x39, 0xe1, + 0x94, 0x95, 0x65, 0x2e, 0x39, 0xb3, 0x12, 0x94, 0xa1, 0x52, 0x59, 0xa1, 0xf9, 0x01, 0x93, 0x6a, + 0x9f, 0x71, 0x0e, 0x95, 0xb2, 0x86, 0x72, 0x50, 0x56, 0x43, 0x9e, 0x0b, 0x4d, 0xa7, 0x9b, 0xf4, + 0xb0, 0x12, 0x7a, 0x46, 0x4a, 0x0d, 0x16, 0x70, 0x2c, 0x27, 0x9c, 0x9c, 0xee, 0x27, 0x67, 0xf4, + 0x93, 0x65, 0x3f, 0x99, 0x6e, 0x0e, 0x1f, 0xad, 0xc1, 0x3c, 0xa5, 0xe0, 0xc0, 0xc3, 0x7e, 0x06, + 0x19, 0xb8, 0x23, 0xad, 0x4f, 0x3e, 0x7b, 0x23, 0x03, 0xc8, 0x72, 0x41, 0x59, 0x29, 0x29, 0x53, + 0x0a, 0xac, 0x1f, 0xca, 0x55, 0x23, 0x8b, 0x6e, 0xee, 0xd5, 0xb3, 0xef, 0xfc, 0xc2, 0x3d, 0x6c, + 0x68, 0x89, 0x38, 0xac, 0x84, 0xb1, 0xb8, 0x8f, 0x36, 0xe0, 0x95, 0x12, 0x7a, 0x10, 0xdc, 0x0e, + 0xee, 0x5c, 0x4a, 0x9a, 0x00, 0xdf, 0x47, 0x57, 0x39, 0x28, 0x25, 0x78, 0xad, 0xb5, 0x2f, 0xd3, + 0x41, 0xb7, 0xae, 0x6e, 0x0f, 0xbe, 0x7d, 0xbe, 0xd5, 0x9f, 0xb1, 0x22, 0x1f, 0x47, 0xad, 0x72, + 0x94, 0x5c, 0x59, 0xc6, 0x3b, 0x69, 0x34, 0x46, 0xe1, 0x9f, 0xa8, 0xa6, 0x04, 0x65, 0x04, 0x1e, + 0xa0, 0x8b, 0x2c, 0x4d, 0xb5, 0x30, 0xc6, 0x83, 0x7f, 0x86, 0x51, 0x1f, 0x61, 0xd7, 0xbb, 0xcb, + 0x34, 0x2b, 0x8c, 0x1f, 0x33, 0x92, 0xe8, 0x7a, 0x2b, 0xeb, 0x65, 0x12, 0xd4, 0x2b, 0x5d, 0xc6, + 0xa9, 0x5c, 0x8e, 0xc7, 0x64, 0x75, 0x73, 0x88, 0xd7, 0xf4, 0x4a, 0xf1, 0xf7, 0x0b, 0x68, 0xc3, + 0xb1, 0xf0, 0xbb, 0x2e, 0xba, 0xf6, 0xdb, 0x0a, 0x78, 0x6f, 0x1d, 0xc6, 0x5f, 0x4d, 0x18, 0x26, + 0xe7, 0x29, 0xd9, 0x5c, 0x4d, 0xf4, 0xe2, 0xf5, 0x87, 0xaf, 0x6f, 0xbb, 0xcf, 0xf0, 0x53, 0xea, + 0xdf, 0xde, 0xbf, 0xbc, 0x39, 0xe7, 0xbe, 0xa1, 0x47, 0xee, 0x7b, 0x4c, 0x97, 0xa6, 0x1a, 0x7a, + 0xd4, 0x72, 0xfc, 0x18, 0x7f, 0x0c, 0x50, 0xaf, 0xb9, 0x39, 0xfc, 0x78, 0xed, 0xf1, 0x5b, 0x26, + 0x0f, 0x9f, 0xfc, 0xb7, 0x8e, 0xdf, 0x7d, 0xec, 0x76, 0xbf, 0x8b, 0xe3, 0x55, 0x76, 0x6f, 0xec, + 0xdf, 0x7e, 0xf9, 0x7e, 0x1e, 0x06, 0x27, 0xf3, 0x30, 0xf8, 0x32, 0x0f, 0x83, 0x37, 0x8b, 0xb0, + 0x73, 0xb2, 0x08, 0x3b, 0x9f, 0x16, 0x61, 0xe7, 0xf9, 0x6e, 0x26, 0xed, 0x41, 0x35, 0x21, 0x1c, + 0x0a, 0xca, 0xc1, 0x14, 0x60, 0x6a, 0xf9, 0x51, 0x06, 0x74, 0xba, 0x45, 0x0b, 0x48, 0xab, 0x5c, + 0x98, 0x06, 0x16, 0xdf, 0x1b, 0x2d, 0x79, 0xa3, 0xb3, 0x78, 0x76, 0x56, 0x0a, 0x33, 0xe9, 0xb9, + 0x9f, 0x74, 0xeb, 0x47, 0x00, 0x00, 0x00, 0xff, 0xff, 0x0d, 0xa8, 0xc5, 0xaa, 0x93, 0x04, 0x00, + 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -156,6 +267,8 @@ const _ = grpc.SupportPackageIsVersion4 // // 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) } @@ -168,6 +281,15 @@ 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...) @@ -179,6 +301,8 @@ func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts . // 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) } @@ -187,6 +311,9 @@ type QueryServer interface { 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") } @@ -195,6 +322,24 @@ 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 { @@ -217,6 +362,10 @@ 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, @@ -226,6 +375,73 @@ var _Query_serviceDesc = grpc.ServiceDesc{ 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) @@ -295,6 +511,36 @@ func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { 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 @@ -323,6 +569,202 @@ func sovQuery(x uint64) (n int) { 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 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 index a9ac1bc03bb..776716ffa61 100644 --- a/modules/apps/27-interchain-accounts/controller/types/query.pb.gw.go +++ b/modules/apps/27-interchain-accounts/controller/types/query.pb.gw.go @@ -31,6 +31,82 @@ var _ = runtime.String var _ = utilities.NewDoubleArray var _ = descriptor.ForMessage +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 @@ -55,6 +131,26 @@ func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshal // Note that using this registration option will cause many gRPC library features (such as grpc.SendHeader, etc) 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() + 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) + 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() @@ -116,6 +212,26 @@ func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc // "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() @@ -140,9 +256,13 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie } 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(true))) + 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(true))) ) var ( + forward_Query_InterchainAccount_0 = runtime.ForwardResponseMessage + forward_Query_Params_0 = runtime.ForwardResponseMessage ) diff --git a/modules/apps/27-interchain-accounts/host/ibc_module_test.go b/modules/apps/27-interchain-accounts/host/ibc_module_test.go index 56baa70e847..443b59db22a 100644 --- a/modules/apps/27-interchain-accounts/host/ibc_module_test.go +++ b/modules/apps/27-interchain-accounts/host/ibc_module_test.go @@ -10,7 +10,6 @@ import ( "github.com/gogo/protobuf/proto" "github.com/stretchr/testify/suite" abcitypes "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" tmprotostate "github.com/tendermint/tendermint/proto/tendermint/state" tmstate "github.com/tendermint/tendermint/state" @@ -24,12 +23,6 @@ import ( ) var ( - // TODO: Cosmos-SDK ADR-28: Update crypto.AddressHash() when sdk uses address.Module() - // https://github.com/cosmos/cosmos-sdk/issues/10225 - // - // TestAccAddress defines a resuable bech32 address for testing purposes - TestAccAddress = icatypes.GenerateAddress(sdk.AccAddress(crypto.AddressHash([]byte(icatypes.ModuleName))), ibctesting.FirstConnectionID, TestPortID) - // TestOwnerAddress defines a reusable bech32 address for testing purposes TestOwnerAddress = "cosmos17dtl0mjt3t77kpuhg2edqzjpszulwhgzuj9ljs" @@ -148,10 +141,20 @@ func (suite *InterchainAccountsTestSuite) TestOnChanOpenTry() { malleate func() expPass bool }{ - { "success", func() {}, true, }, + { + "account address generation is block dependent", func() { + icaHostAccount := icatypes.GenerateUniqueAddress(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", sdk.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.coordinator.CommitBlock(suite.chainB) + }, true, + }, { "host submodule disabled", func() { suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), types.NewParams(false, []string{})) @@ -166,7 +169,6 @@ func (suite *InterchainAccountsTestSuite) TestOnChanOpenTry() { ) (string, error) { return "", fmt.Errorf("mock ica auth fails") } - }, true, }, { @@ -219,14 +221,16 @@ func (suite *InterchainAccountsTestSuite) TestOnChanOpenTry() { if tc.expPass { 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().Error(err) suite.Require().Equal("", version) } - }) } - } // Test initiating a ChanOpenAck using the host chain instead of the controller chain @@ -270,7 +274,6 @@ func (suite *InterchainAccountsTestSuite) TestOnChanOpenConfirm() { malleate func() expPass bool }{ - { "success", func() {}, true, }, @@ -287,7 +290,6 @@ func (suite *InterchainAccountsTestSuite) TestOnChanOpenConfirm() { ) error { return fmt.Errorf("mock ica auth fails") } - }, true, }, } @@ -324,10 +326,8 @@ func (suite *InterchainAccountsTestSuite) TestOnChanOpenConfirm() { } else { suite.Require().Error(err) } - }) } - } // OnChanCloseInit on host (chainB) @@ -352,16 +352,13 @@ func (suite *InterchainAccountsTestSuite) TestOnChanCloseInit() { } func (suite *InterchainAccountsTestSuite) TestOnChanCloseConfirm() { - var ( - path *ibctesting.Path - ) + var path *ibctesting.Path testCases := []struct { name string malleate func() expPass bool }{ - { "success", func() {}, true, }, @@ -392,15 +389,12 @@ func (suite *InterchainAccountsTestSuite) TestOnChanCloseConfirm() { } else { suite.Require().Error(err) } - }) } } func (suite *InterchainAccountsTestSuite) TestOnRecvPacket() { - var ( - packetData []byte - ) + var packetData []byte testCases := []struct { name string malleate func() @@ -504,14 +498,11 @@ func (suite *InterchainAccountsTestSuite) TestOnRecvPacket() { } else { suite.Require().False(ack.Success()) } - }) } - } func (suite *InterchainAccountsTestSuite) TestOnAcknowledgementPacket() { - testCases := []struct { name string malleate func() @@ -553,7 +544,7 @@ func (suite *InterchainAccountsTestSuite) TestOnAcknowledgementPacket() { 0, ) - err = cbs.OnAcknowledgementPacket(suite.chainB.GetContext(), packet, []byte("ackBytes"), TestAccAddress) + err = cbs.OnAcknowledgementPacket(suite.chainB.GetContext(), packet, []byte("ackBytes"), nil) if tc.expPass { suite.Require().NoError(err) @@ -565,7 +556,6 @@ func (suite *InterchainAccountsTestSuite) TestOnAcknowledgementPacket() { } func (suite *InterchainAccountsTestSuite) TestOnTimeoutPacket() { - testCases := []struct { name string malleate func() @@ -607,7 +597,7 @@ func (suite *InterchainAccountsTestSuite) TestOnTimeoutPacket() { 0, ) - err = cbs.OnTimeoutPacket(suite.chainA.GetContext(), packet, TestAccAddress) + err = cbs.OnTimeoutPacket(suite.chainA.GetContext(), packet, nil) if tc.expPass { suite.Require().NoError(err) @@ -633,21 +623,28 @@ func (suite *InterchainAccountsTestSuite) fundICAWallet(ctx sdk.Context, portID 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 -// by opening a new channel on the associated portID +// 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() { - // create channel + init interchain account on a particular port path := NewICAPath(suite.chainA, suite.chainB) suite.coordinator.SetupConnections(path) + 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, sdk.NewInt(100000))) + tokenAmt = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.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, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10000)))) - interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID) + 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) - tokenAmt := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(5000))) msg := &banktypes.MsgSend{ FromAddress: interchainAccountAddr, ToAddress: suite.chainB.SenderAccount.GetAddress().String(), @@ -681,8 +678,7 @@ func (suite *InterchainAccountsTestSuite) TestControlAccountAfterChannelClose() icaAddr, err := sdk.AccAddressFromBech32(interchainAccountAddr) suite.Require().NoError(err) - hasBalance := suite.chainB.GetSimApp().BankKeeper.HasBalance(suite.chainB.GetContext(), icaAddr, sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: sdk.NewInt(5000)}) - suite.Require().True(hasBalance) + suite.assertBalance(icaAddr, expBalAfterFirstSend) // close the channel err = path.EndpointA.SetChannelClosed() @@ -708,13 +704,19 @@ func (suite *InterchainAccountsTestSuite) TestControlAccountAfterChannelClose() err = path.RelayPacket(packetRelay) suite.Require().NoError(err) // relay committed - // check that the ica balance is updated - hasBalance = suite.chainB.GetSimApp().BankKeeper.HasBalance(suite.chainB.GetContext(), icaAddr, sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: sdk.NewInt(0)}) - suite.Require().True(hasBalance) + 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) } // The safety of including SDK MsgResponses in the acknowledgement rests // on the inclusion of the abcitypes.ResponseDeliverTx.Data in the + // abcitypes.ResposneDeliverTx hash. If the abcitypes.ResponseDeliverTx.Data // gets removed from consensus they must no longer be used in the packet // acknowledgement. diff --git a/modules/apps/27-interchain-accounts/host/keeper/account.go b/modules/apps/27-interchain-accounts/host/keeper/account.go index d37cc21f1c2..91f2151594c 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/account.go +++ b/modules/apps/27-interchain-accounts/host/keeper/account.go @@ -2,6 +2,7 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" icatypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/types" @@ -10,6 +11,7 @@ import ( // RegisterInterchainAccount attempts to create a new account using the provided address and // stores it in state keyed by the provided connection and port identifiers // If an account for the provided address already exists this function returns early (no-op) +// NOTE: This function is deprecated! func (k Keeper) RegisterInterchainAccount(ctx sdk.Context, connectionID, controllerPortID string, accAddress sdk.AccAddress) { if acc := k.accountKeeper.GetAccount(ctx, accAddress); acc != nil { return @@ -25,3 +27,26 @@ func (k Keeper) RegisterInterchainAccount(ctx sdk.Context, connectionID, control k.SetInterchainAccountAddress(ctx, connectionID, controllerPortID, interchainAccount.Address) } + +// 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.GenerateUniqueAddress(ctx, connectionID, controllerPortID) + + if acc := k.accountKeeper.GetAccount(ctx, accAddress); acc != nil { + return nil, sdkerrors.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/account_test.go b/modules/apps/27-interchain-accounts/host/keeper/account_test.go deleted file mode 100644 index df1966277e4..00000000000 --- a/modules/apps/27-interchain-accounts/host/keeper/account_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package keeper_test - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - - icatypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/types" - ibctesting "github.com/cosmos/ibc-go/v3/testing" -) - -func (suite *KeeperTestSuite) TestRegisterInterchainAccount() { - suite.SetupTest() - - path := NewICAPath(suite.chainA, suite.chainB) - suite.coordinator.SetupConnections(path) - - //RegisterInterchainAccount - 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) -} diff --git a/modules/apps/27-interchain-accounts/host/keeper/genesis_test.go b/modules/apps/27-interchain-accounts/host/keeper/genesis_test.go index 138d713cf67..6e8638ba42c 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/genesis_test.go +++ b/modules/apps/27-interchain-accounts/host/keeper/genesis_test.go @@ -10,6 +10,7 @@ import ( func (suite *KeeperTestSuite) TestInitGenesis() { suite.SetupTest() + interchainAccAddr := icatypes.GenerateUniqueAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, TestPortID) genesisState := icatypes.HostGenesisState{ ActiveChannels: []icatypes.ActiveChannel{ { @@ -22,7 +23,7 @@ func (suite *KeeperTestSuite) TestInitGenesis() { { ConnectionId: ibctesting.FirstConnectionID, PortId: TestPortID, - AccountAddress: TestAccAddress.String(), + AccountAddress: interchainAccAddr.String(), }, }, Port: icatypes.PortID, @@ -36,7 +37,7 @@ func (suite *KeeperTestSuite) TestInitGenesis() { accountAdrr, found := suite.chainA.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), ibctesting.FirstConnectionID, TestPortID) suite.Require().True(found) - suite.Require().Equal(TestAccAddress.String(), accountAdrr) + suite.Require().Equal(interchainAccAddr.String(), accountAdrr) expParams := types.NewParams(false, nil) params := suite.chainA.GetSimApp().ICAHostKeeper.GetParams(suite.chainA.GetContext()) @@ -52,12 +53,15 @@ func (suite *KeeperTestSuite) TestExportGenesis() { 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(TestAccAddress.String(), genesisState.InterchainAccounts[0].AccountAddress) + suite.Require().Equal(interchainAccAddr, genesisState.InterchainAccounts[0].AccountAddress) suite.Require().Equal(path.EndpointA.ChannelConfig.PortID, genesisState.InterchainAccounts[0].PortId) suite.Require().Equal(icatypes.PortID, genesisState.GetPort()) diff --git a/modules/apps/27-interchain-accounts/host/keeper/handshake.go b/modules/apps/27-interchain-accounts/host/keeper/handshake.go index 11a7c7a378e..663ac058e5f 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/handshake.go +++ b/modules/apps/27-interchain-accounts/host/keeper/handshake.go @@ -2,7 +2,6 @@ package keeper import ( "fmt" - "strings" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -35,10 +34,6 @@ func (k Keeper) OnChanOpenTry( return "", sdkerrors.Wrapf(icatypes.ErrInvalidHostPort, "expected %s, got %s", icatypes.PortID, portID) } - if !strings.HasPrefix(counterparty.PortId, icatypes.PortPrefix) { - return "", sdkerrors.Wrapf(icatypes.ErrInvalidControllerPort, "expected %s{owner-account-address}, got %s", icatypes.PortPrefix, counterparty.PortId) - } - var metadata icatypes.Metadata if err := icatypes.ModuleCdc.UnmarshalJSON([]byte(counterpartyVersion), &metadata); err != nil { return "", sdkerrors.Wrapf(icatypes.ErrUnknownDataType, "cannot unmarshal ICS-27 interchain accounts metadata") @@ -70,10 +65,25 @@ func (k Keeper) OnChanOpenTry( return "", sdkerrors.Wrapf(err, "failed to claim capability for channel %s on port %s", channelID, portID) } - accAddress := icatypes.GenerateAddress(k.accountKeeper.GetModuleAddress(icatypes.ModuleName), metadata.HostConnectionId, counterparty.PortId) + var ( + accAddress sdk.AccAddress + err error + ) + + interchainAccAddr, found := k.GetInterchainAccountAddress(ctx, metadata.HostConnectionId, counterparty.PortId) + if found { + // reopening an interchain account + accAddress = sdk.MustAccAddressFromBech32(interchainAccAddr) + if _, ok := k.accountKeeper.GetAccount(ctx, accAddress).(*icatypes.InterchainAccount); !ok { + return "", sdkerrors.Wrapf(icatypes.ErrInvalidAccountReopening, "existing account address %s, does not have interchain account type", accAddress) + } - // Register interchain account if it does not already exist - k.RegisterInterchainAccount(ctx, metadata.HostConnectionId, counterparty.PortId, accAddress) + } else { + accAddress, err = k.createInterchainAccount(ctx, metadata.HostConnectionId, counterparty.PortId) + if err != nil { + return "", err + } + } metadata.Address = accAddress.String() versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) @@ -110,6 +120,5 @@ func (k Keeper) OnChanCloseConfirm( 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 index 0cac2912ccb..a34c7095f34 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/handshake_test.go +++ b/modules/apps/27-interchain-accounts/host/keeper/handshake_test.go @@ -1,14 +1,43 @@ package keeper_test import ( + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + hosttypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/host/types" icatypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/types" channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" host "github.com/cosmos/ibc-go/v3/modules/core/24-host" ibctesting "github.com/cosmos/ibc-go/v3/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) + + err = path.EndpointA.SetChannelClosed() + suite.Require().NoError(err) + + err = path.EndpointB.SetChannelClosed() + suite.Require().NoError(err) + + 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() { var ( channel *channeltypes.Channel @@ -22,25 +51,91 @@ func (suite *KeeperTestSuite) TestOnChanOpenTry() { malleate func() expPass bool }{ - { "success", - func() { - path.EndpointB.SetChannel(*channel) - }, + func() {}, true, }, { "success - reopening closed active channel", func() { - // create a new channel and set it in state - ch := channeltypes.NewChannel(channeltypes.CLOSED, channeltypes.ORDERED, channeltypes.NewCounterparty(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID), []string{path.EndpointA.ConnectionID}, TestVersion) - suite.chainB.GetSimApp().GetIBCKeeper().ChannelKeeper.SetChannel(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, ch) + // create interchain account + // undo setup + path.EndpointB.ChannelID = "" + err := suite.chainB.App.GetScopedIBCKeeper().ReleaseCapability(suite.chainB.GetContext(), chanCap) + suite.Require().NoError(err) - // set the active channelID in state - suite.chainB.GetSimApp().ICAHostKeeper.SetActiveChannelID(suite.chainB.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID, path.EndpointB.ChannelID) + suite.openAndCloseChannel(path) }, true, }, + { + "success - reopening account with new address", + func() { + // create interchain account + // undo setup + path.EndpointB.ChannelID = "" + err := suite.chainB.App.GetScopedIBCKeeper().ReleaseCapability(suite.chainB.GetContext(), chanCap) + suite.Require().NoError(err) + + 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) + }, true, + }, + { + "reopening account fails - no existing account", + func() { + // create interchain account + // undo setup + path.EndpointB.ChannelID = "" + err := suite.chainB.App.GetScopedIBCKeeper().ReleaseCapability(suite.chainB.GetContext(), chanCap) + suite.Require().NoError(err) + + 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) + }, false, + }, + { + "reopening account fails - existing account is not interchain account type", + func() { + // create interchain account + // undo setup + path.EndpointB.ChannelID = "" + err := suite.chainB.App.GetScopedIBCKeeper().ReleaseCapability(suite.chainB.GetContext(), chanCap) + suite.Require().NoError(err) + + 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) + baseAcc := authtypes.NewBaseAccountWithAddress(accAddress) + suite.chainB.GetSimApp().AccountKeeper.SetAccount(suite.chainB.GetContext(), baseAcc) + }, false, + }, + { + "account already exists", + func() { + interchainAccAddr := icatypes.GenerateUniqueAddress(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", sdk.NewInt(1))}) + suite.Require().NoError(err) + suite.Require().True(suite.chainB.GetSimApp().AccountKeeper.HasAccount(suite.chainB.GetContext(), interchainAccAddr)) + }, + false, + }, { "invalid metadata - previous metadata is different", func() { @@ -49,15 +144,17 @@ func (suite *KeeperTestSuite) TestOnChanOpenTry() { 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) + suite.chainB.GetSimApp().ICAHostKeeper.SetActiveChannelID(suite.chainB.GetContext(), path.EndpointB.ConnectionID, path.EndpointA.ChannelConfig.PortID, path.EndpointB.ChannelID) - // modify metadata + // 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) - path.EndpointA.ChannelConfig.Version = string(versionBytes) + channel.Version = string(versionBytes) + + path.EndpointB.SetChannel(*channel) }, false, }, { @@ -74,13 +171,6 @@ func (suite *KeeperTestSuite) TestOnChanOpenTry() { }, false, }, - { - "invalid counterparty port ID", - func() { - channel.Counterparty.PortId = "invalid-port-id" - }, - false, - }, { "connection not found", func() { @@ -219,6 +309,16 @@ func (suite *KeeperTestSuite) TestOnChanOpenTry() { if tc.expPass { 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) } else { suite.Require().Error(err) suite.Require().Equal("", version) @@ -228,16 +328,13 @@ func (suite *KeeperTestSuite) TestOnChanOpenTry() { } func (suite *KeeperTestSuite) TestOnChanOpenConfirm() { - var ( - path *ibctesting.Path - ) + var path *ibctesting.Path testCases := []struct { name string malleate func() expPass bool }{ - { "success", func() {}, true, }, @@ -279,22 +376,18 @@ func (suite *KeeperTestSuite) TestOnChanOpenConfirm() { } else { suite.Require().Error(err) } - }) } } func (suite *KeeperTestSuite) TestOnChanCloseConfirm() { - var ( - path *ibctesting.Path - ) + var path *ibctesting.Path testCases := []struct { name string malleate func() expPass bool }{ - { "success", func() {}, true, }, @@ -320,7 +413,6 @@ func (suite *KeeperTestSuite) TestOnChanCloseConfirm() { } else { suite.Require().Error(err) } - }) } } diff --git a/modules/apps/27-interchain-accounts/host/keeper/keeper.go b/modules/apps/27-interchain-accounts/host/keeper/keeper.go index ea3f8205c87..8f2f345da1b 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/keeper.go +++ b/modules/apps/27-interchain-accounts/host/keeper/keeper.go @@ -39,7 +39,6 @@ func NewKeeper( channelKeeper icatypes.ChannelKeeper, portKeeper icatypes.PortKeeper, accountKeeper icatypes.AccountKeeper, scopedKeeper capabilitykeeper.ScopedKeeper, msgRouter *baseapp.MsgServiceRouter, ) Keeper { - // ensure ibc interchain accounts module account is set if addr := accountKeeper.GetModuleAddress(icatypes.ModuleName); addr == nil { panic("the Interchain Accounts module account has not been set") diff --git a/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go b/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go index 96c80c45f99..9857e4a99fc 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go +++ b/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go @@ -3,9 +3,7 @@ package keeper_test import ( "testing" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/suite" - "github.com/tendermint/tendermint/crypto" icatypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/types" channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" @@ -13,12 +11,6 @@ import ( ) var ( - // TODO: Cosmos-SDK ADR-28: Update crypto.AddressHash() when sdk uses address.Module() - // https://github.com/cosmos/cosmos-sdk/issues/10225 - // - // TestAccAddress defines a resuable bech32 address for testing purposes - TestAccAddress = icatypes.GenerateAddress(sdk.AccAddress(crypto.AddressHash([]byte(icatypes.ModuleName))), ibctesting.FirstConnectionID, TestPortID) - // TestOwnerAddress defines a reusable bech32 address for testing purposes TestOwnerAddress = "cosmos17dtl0mjt3t77kpuhg2edqzjpszulwhgzuj9ljs" @@ -137,11 +129,10 @@ func (suite *KeeperTestSuite) TestGetInterchainAccountAddress() { suite.Require().NoError(err) counterpartyPortID := path.EndpointA.ChannelConfig.PortID - expectedAddr := icatypes.GenerateAddress(suite.chainA.GetSimApp().AccountKeeper.GetModuleAddress(icatypes.ModuleName), ibctesting.FirstConnectionID, counterpartyPortID) retrievedAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, counterpartyPortID) suite.Require().True(found) - suite.Require().Equal(expectedAddr.String(), retrievedAddr) + suite.Require().NotEmpty(retrievedAddr) retrievedAddr, found = suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, "invalid port") suite.Require().False(found) @@ -196,13 +187,16 @@ func (suite *KeeperTestSuite) TestGetAllInterchainAccounts() { 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 := []icatypes.RegisteredInterchainAccount{ { ConnectionId: ibctesting.FirstConnectionID, PortId: TestPortID, - AccountAddress: TestAccAddress.String(), + AccountAddress: interchainAccAddr, }, { ConnectionId: ibctesting.FirstConnectionID, diff --git a/modules/apps/27-interchain-accounts/host/keeper/relay_test.go b/modules/apps/27-interchain-accounts/host/keeper/relay_test.go index fda31c34ef0..d92be7227aa 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/relay_test.go +++ b/modules/apps/27-interchain-accounts/host/keeper/relay_test.go @@ -29,6 +29,45 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() { malleate func() expPass bool }{ + { + "interchain account successfully executes an arbitrary message type using the * (allow all message types) param", + func() { + interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(found) + + // Populate the gov keeper in advance with an active proposal + testProposal := &govtypes.TextProposal{ + Title: "IBC Gov Proposal", + Description: "tokens for all!", + } + + proposal, err := govtypes.NewProposal(testProposal, govtypes.DefaultStartingProposalID, time.Now(), time.Now().Add(time.Hour)) + suite.Require().NoError(err) + + suite.chainB.GetSimApp().GovKeeper.SetProposal(suite.chainB.GetContext(), proposal) + suite.chainB.GetSimApp().GovKeeper.ActivateVotingPeriod(suite.chainB.GetContext(), proposal) + + msg := &govtypes.MsgVote{ + ProposalId: govtypes.DefaultStartingProposalID, + Voter: interchainAccountAddr, + Option: govtypes.OptionYes, + } + + data, err := icatypes.SerializeCosmosTx(suite.chainA.GetSimApp().AppCodec(), []sdk.Msg{msg}) + 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) + }, + true, + }, { "interchain account successfully executes banktypes.MsgSend", func() { diff --git a/modules/apps/27-interchain-accounts/host/types/ack_test.go b/modules/apps/27-interchain-accounts/host/types/ack_test.go index bc4e2d07afc..6d2e6d1a95a 100644 --- a/modules/apps/27-interchain-accounts/host/types/ack_test.go +++ b/modules/apps/27-interchain-accounts/host/types/ack_test.go @@ -97,5 +97,4 @@ func (suite *TypesTestSuite) TestAcknowledgementError() { suite.Require().Equal(ack, ackSameABCICode) suite.Require().NotEqual(ack, ackDifferentABCICode) - } diff --git a/modules/apps/27-interchain-accounts/host/types/keys.go b/modules/apps/27-interchain-accounts/host/types/keys.go index 74c93cdb605..7f1a04facb5 100644 --- a/modules/apps/27-interchain-accounts/host/types/keys.go +++ b/modules/apps/27-interchain-accounts/host/types/keys.go @@ -14,6 +14,11 @@ const ( // 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] == "*" { + return true + } + for _, v := range allowMsgs { if v == sdk.MsgTypeURL(msg) { return true diff --git a/modules/apps/27-interchain-accounts/module_test.go b/modules/apps/27-interchain-accounts/module_test.go index 59517ab40e4..ff44eed4ff0 100644 --- a/modules/apps/27-interchain-accounts/module_test.go +++ b/modules/apps/27-interchain-accounts/module_test.go @@ -125,8 +125,6 @@ func (suite *InterchainAccountsTestSuite) TestInitModule() { suite.Require().True(app.IBCKeeper.PortKeeper.IsBound(ctx, types.PortID)) } - }) } - } diff --git a/modules/apps/27-interchain-accounts/types/account.go b/modules/apps/27-interchain-accounts/types/account.go index 9cd3fe4d327..84c750515b9 100644 --- a/modules/apps/27-interchain-accounts/types/account.go +++ b/modules/apps/27-interchain-accounts/types/account.go @@ -41,10 +41,24 @@ type interchainAccountPretty struct { // GenerateAddress returns an sdk.AccAddress derived using the provided module account address and connection and port identifiers. // The sdk.AccAddress returned is a sub-address of the module account, using the host chain connection ID and controller chain's port ID as the derivation key +// Deprecated: this function is deprecated! Please use GenerateUniqueAddress in favour of GenerateAddress func GenerateAddress(moduleAccAddr sdk.AccAddress, connectionID, portID string) sdk.AccAddress { return sdk.AccAddress(sdkaddress.Derive(moduleAccAddr, []byte(connectionID+portID))) } +// GenerateUniqueAddress 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 GenerateUniqueAddress(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...) + buf = append(buf, header.DataHash...) + + 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 { @@ -106,7 +120,6 @@ func (ia InterchainAccount) MarshalYAML() ([]byte, error) { Sequence: ia.Sequence, AccountOwner: ia.AccountOwner, }) - if err != nil { return nil, err } @@ -128,7 +141,6 @@ func (ia InterchainAccount) MarshalJSON() ([]byte, error) { Sequence: ia.Sequence, AccountOwner: ia.AccountOwner, }) - if err != nil { return nil, err } diff --git a/modules/apps/27-interchain-accounts/types/account_test.go b/modules/apps/27-interchain-accounts/types/account_test.go index 13acc610152..228121e81b6 100644 --- a/modules/apps/27-interchain-accounts/types/account_test.go +++ b/modules/apps/27-interchain-accounts/types/account_test.go @@ -42,11 +42,11 @@ func TestTypesTestSuite(t *testing.T) { suite.Run(t, new(TypesTestSuite)) } -func (suite *TypesTestSuite) TestGenerateAddress() { - addr := types.GenerateAddress([]byte{}, "test-connection-id", "test-port-id") +func (suite *TypesTestSuite) TestGenerateUniqueAddress() { + addr := types.GenerateUniqueAddress(suite.chainA.GetContext(), "test-connection-id", "test-port-id") accAddr, err := sdk.AccAddressFromBech32(addr.String()) - suite.Require().NoError(err, "TestGenerateAddress failed") + suite.Require().NoError(err, "TestGenerateUniqueAddress failed") suite.Require().NotEmpty(accAddr) } diff --git a/modules/apps/27-interchain-accounts/types/codec.go b/modules/apps/27-interchain-accounts/types/codec.go index a5a0922cabd..033c264f9e7 100644 --- a/modules/apps/27-interchain-accounts/types/codec.go +++ b/modules/apps/27-interchain-accounts/types/codec.go @@ -8,14 +8,12 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" ) -var ( - // 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. - ModuleCdc = codec.NewProtoCodec(codectypes.NewInterfaceRegistry()) -) +// 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 concrete InterchainAccount implementation against the associated // x/auth AccountI and GenesisAccount interfaces diff --git a/modules/apps/27-interchain-accounts/types/codec_test.go b/modules/apps/27-interchain-accounts/types/codec_test.go index e027fda9346..b18b018332a 100644 --- a/modules/apps/27-interchain-accounts/types/codec_test.go +++ b/modules/apps/27-interchain-accounts/types/codec_test.go @@ -148,5 +148,4 @@ func (suite *TypesTestSuite) TestDeserializeAndSerializeCosmosTxWithAmino() { bz, err := types.DeserializeCosmosTx(marshaler, []byte{0x10, 0}) suite.Require().Error(err) suite.Require().Empty(bz) - } diff --git a/modules/apps/27-interchain-accounts/types/errors.go b/modules/apps/27-interchain-accounts/types/errors.go index 7bb391dbe93..5c80da561bc 100644 --- a/modules/apps/27-interchain-accounts/types/errors.go +++ b/modules/apps/27-interchain-accounts/types/errors.go @@ -22,4 +22,5 @@ var ( ErrInvalidHostPort = sdkerrors.Register(ModuleName, 16, "invalid host port") ErrInvalidTimeoutTimestamp = sdkerrors.Register(ModuleName, 17, "timeout timestamp must be in the future") ErrInvalidCodec = sdkerrors.Register(ModuleName, 18, "codec is not supported") + ErrInvalidAccountReopening = sdkerrors.Register(ModuleName, 19, "invalid account reopening") ) diff --git a/modules/apps/27-interchain-accounts/types/genesis_test.go b/modules/apps/27-interchain-accounts/types/genesis_test.go index 7e6ffdc280b..bbdbc8d14d5 100644 --- a/modules/apps/27-interchain-accounts/types/genesis_test.go +++ b/modules/apps/27-interchain-accounts/types/genesis_test.go @@ -8,9 +8,7 @@ import ( ) func (suite *TypesTestSuite) TestValidateGenesisState() { - var ( - genesisState types.GenesisState - ) + var genesisState types.GenesisState testCases := []struct { name string @@ -63,9 +61,7 @@ func (suite *TypesTestSuite) TestValidateGenesisState() { } func (suite *TypesTestSuite) TestValidateControllerGenesisState() { - var ( - genesisState types.ControllerGenesisState - ) + var genesisState types.ControllerGenesisState testCases := []struct { name string @@ -188,9 +184,7 @@ func (suite *TypesTestSuite) TestValidateControllerGenesisState() { } func (suite *TypesTestSuite) TestValidateHostGenesisState() { - var ( - genesisState types.HostGenesisState - ) + var genesisState types.HostGenesisState testCases := []struct { name string diff --git a/modules/apps/27-interchain-accounts/types/keys.go b/modules/apps/27-interchain-accounts/types/keys.go index 2bf05ffda3f..deb5602c1f3 100644 --- a/modules/apps/27-interchain-accounts/types/keys.go +++ b/modules/apps/27-interchain-accounts/types/keys.go @@ -25,6 +25,9 @@ const ( // 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 ( diff --git a/modules/apps/27-interchain-accounts/types/metadata_test.go b/modules/apps/27-interchain-accounts/types/metadata_test.go index 05a1b457c38..3bda2549c07 100644 --- a/modules/apps/27-interchain-accounts/types/metadata_test.go +++ b/modules/apps/27-interchain-accounts/types/metadata_test.go @@ -7,7 +7,6 @@ import ( // use TestVersion as metadata being compared against func (suite *TypesTestSuite) TestIsPreviousMetadataEqual() { - var ( metadata types.Metadata previousVersion string @@ -127,7 +126,6 @@ func (suite *TypesTestSuite) TestIsPreviousMetadataEqual() { } func (suite *TypesTestSuite) TestValidateControllerMetadata() { - var metadata types.Metadata testCases := []struct { @@ -269,7 +267,6 @@ func (suite *TypesTestSuite) TestValidateControllerMetadata() { } func (suite *TypesTestSuite) TestValidateHostMetadata() { - var metadata types.Metadata testCases := []struct { diff --git a/modules/apps/27-interchain-accounts/types/packet.go b/modules/apps/27-interchain-accounts/types/packet.go index e7669a77fc9..59419f8f302 100644 --- a/modules/apps/27-interchain-accounts/types/packet.go +++ b/modules/apps/27-interchain-accounts/types/packet.go @@ -16,7 +16,7 @@ func (iapd InterchainAccountPacketData) ValidateBasic() error { return sdkerrors.Wrap(ErrInvalidOutgoingData, "packet data type cannot be unspecified") } - if iapd.Data == nil { + if len(iapd.Data) == 0 { return sdkerrors.Wrap(ErrInvalidOutgoingData, "packet data cannot be empty") } diff --git a/modules/apps/27-interchain-accounts/types/packet_test.go b/modules/apps/27-interchain-accounts/types/packet_test.go index 840ca529851..92ce68c6ff1 100644 --- a/modules/apps/27-interchain-accounts/types/packet_test.go +++ b/modules/apps/27-interchain-accounts/types/packet_test.go @@ -33,13 +33,22 @@ func (suite *TypesTestSuite) TestValidateBasic() { "type unspecified", types.InterchainAccountPacketData{ Type: types.UNSPECIFIED, - Data: []byte("data"), + Data: []byte("data"), Memo: "memo", }, false, }, { "empty data", + types.InterchainAccountPacketData{ + Type: types.EXECUTE_TX, + Data: []byte{}, + Memo: "memo", + }, + false, + }, + { + "nil data", types.InterchainAccountPacketData{ Type: types.EXECUTE_TX, Data: nil, diff --git a/modules/apps/transfer/client/cli/tx.go b/modules/apps/transfer/client/cli/tx.go index 02e006be5eb..817405b98ec 100644 --- a/modules/apps/transfer/client/cli/tx.go +++ b/modules/apps/transfer/client/cli/tx.go @@ -22,6 +22,7 @@ const ( flagPacketTimeoutHeight = "packet-timeout-height" flagPacketTimeoutTimestamp = "packet-timeout-timestamp" flagAbsoluteTimeouts = "absolute-timeouts" + flagMemo = "memo" ) // NewTransferTxCmd returns the command to create a NewMsgTransfer transaction @@ -76,6 +77,11 @@ corresponding to the counterparty channel. Any timeout set to 0 is disabled.`), return err } + memo, err := cmd.Flags().GetString(flagMemo) + if err != nil { + return err + } + // if the timeouts are not absolute, retrieve latest block height and block timestamp // for the consensus state connected to the destination port/channel if !absoluteTimeouts { @@ -113,6 +119,8 @@ corresponding to the counterparty channel. Any timeout set to 0 is disabled.`), msg := types.NewMsgTransfer( srcPort, srcChannel, coin, sender, receiver, timeoutHeight, timeoutTimestamp, ) + msg.Memo = memo + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) }, } @@ -120,6 +128,7 @@ corresponding to the counterparty channel. Any timeout set to 0 is disabled.`), cmd.Flags().String(flagPacketTimeoutHeight, types.DefaultRelativePacketTimeoutHeight, "Packet timeout block height. The timeout is disabled when set to 0-0.") cmd.Flags().Uint64(flagPacketTimeoutTimestamp, types.DefaultRelativePacketTimeoutTimestamp, "Packet timeout timestamp in nanoseconds from now. Default is 10 minutes. The timeout is disabled when set to 0.") 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/ibc_module.go b/modules/apps/transfer/ibc_module.go index f611c62642f..a80c51826cb 100644 --- a/modules/apps/transfer/ibc_module.go +++ b/modules/apps/transfer/ibc_module.go @@ -194,6 +194,7 @@ func (im IBCModule) OnRecvPacket( sdk.NewAttribute(types.AttributeKeyReceiver, data.Receiver), sdk.NewAttribute(types.AttributeKeyDenom, data.Denom), sdk.NewAttribute(types.AttributeKeyAmount, data.Amount), + sdk.NewAttribute(types.AttributeKeyMemo, data.Memo), sdk.NewAttribute(types.AttributeKeyAckSuccess, fmt.Sprintf("%t", ack.Success())), ), ) @@ -230,6 +231,7 @@ func (im IBCModule) OnAcknowledgementPacket( sdk.NewAttribute(types.AttributeKeyReceiver, data.Receiver), sdk.NewAttribute(types.AttributeKeyDenom, data.Denom), sdk.NewAttribute(types.AttributeKeyAmount, data.Amount), + sdk.NewAttribute(types.AttributeKeyMemo, data.Memo), sdk.NewAttribute(types.AttributeKeyAck, ack.String()), ), ) @@ -276,6 +278,7 @@ func (im IBCModule) OnTimeoutPacket( sdk.NewAttribute(types.AttributeKeyRefundReceiver, data.Sender), sdk.NewAttribute(types.AttributeKeyRefundDenom, data.Denom), sdk.NewAttribute(types.AttributeKeyRefundAmount, data.Amount), + sdk.NewAttribute(types.AttributeKeyMemo, data.Memo), ), ) diff --git a/modules/apps/transfer/ibc_module_test.go b/modules/apps/transfer/ibc_module_test.go index 92d0f30d4c7..7f53e5752f4 100644 --- a/modules/apps/transfer/ibc_module_test.go +++ b/modules/apps/transfer/ibc_module_test.go @@ -23,7 +23,6 @@ func (suite *TransferTestSuite) TestOnChanOpenInit() { malleate func() expPass bool }{ - { "success", func() {}, true, }, @@ -93,7 +92,6 @@ func (suite *TransferTestSuite) TestOnChanOpenInit() { } else { suite.Require().Error(err) } - }) } } @@ -111,7 +109,6 @@ func (suite *TransferTestSuite) TestOnChanOpenTry() { malleate func() expPass bool }{ - { "success", func() {}, true, }, @@ -185,7 +182,6 @@ func (suite *TransferTestSuite) TestOnChanOpenTry() { suite.Require().Error(err) suite.Require().Equal("", version) } - }) } } @@ -198,7 +194,6 @@ func (suite *TransferTestSuite) TestOnChanOpenAck() { malleate func() expPass bool }{ - { "success", func() {}, true, }, @@ -235,7 +230,6 @@ func (suite *TransferTestSuite) TestOnChanOpenAck() { } else { suite.Require().Error(err) } - }) } } diff --git a/modules/apps/transfer/keeper/grpc_query.go b/modules/apps/transfer/keeper/grpc_query.go index 5e7c5e7d295..512e8e58396 100644 --- a/modules/apps/transfer/keeper/grpc_query.go +++ b/modules/apps/transfer/keeper/grpc_query.go @@ -24,7 +24,6 @@ func (q Keeper) DenomTrace(c context.Context, req *types.QueryDenomTraceRequest) } 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)) } @@ -63,7 +62,6 @@ func (q Keeper) DenomTraces(c context.Context, req *types.QueryDenomTracesReques traces = append(traces, result) return nil }) - if err != nil { return nil, err } @@ -110,3 +108,16 @@ func (q Keeper) DenomHash(c context.Context, req *types.QueryDenomHashRequest) ( Hash: denomHash.String(), }, nil } + +// EscrowAddress implements the EscrowAddress gRPC method +func (q Keeper) EscrowAddress(c context.Context, req *types.QueryEscrowAddressRequest) (*types.QueryEscrowAddressResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + addr := types.GetEscrowAddress(req.PortId, req.ChannelId) + + return &types.QueryEscrowAddressResponse{ + EscrowAddress: addr.String(), + }, nil +} diff --git a/modules/apps/transfer/keeper/grpc_query_test.go b/modules/apps/transfer/keeper/grpc_query_test.go index 963abd21577..bc22cf0736a 100644 --- a/modules/apps/transfer/keeper/grpc_query_test.go +++ b/modules/apps/transfer/keeper/grpc_query_test.go @@ -7,6 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/types/query" "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" + ibctesting "github.com/cosmos/ibc-go/v3/testing" ) func (suite *KeeperTestSuite) TestQueryDenomTrace() { @@ -156,7 +157,6 @@ func (suite *KeeperTestSuite) TestQueryParams() { } func (suite *KeeperTestSuite) TestQueryDenomHash() { - reqTrace := types.DenomTrace{ Path: "transfer/channelToA/transfer/channelToB", BaseDenom: "uatom", @@ -221,3 +221,43 @@ func (suite *KeeperTestSuite) TestQueryDenomHash() { }) } } + +func (suite *KeeperTestSuite) TestEscrowAddress() { + var req *types.QueryEscrowAddressRequest + + testCases := []struct { + msg string + malleate func() + expPass bool + }{ + { + "success", + func() { + req = &types.QueryEscrowAddressRequest{ + PortId: ibctesting.TransferPort, + ChannelId: ibctesting.FirstChannelID, + } + }, + true, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := sdk.WrapSDKContext(suite.chainA.GetContext()) + + res, err := suite.queryClient.EscrowAddress(ctx, req) + + if tc.expPass { + suite.Require().NoError(err) + expected := types.GetEscrowAddress(ibctesting.TransferPort, ibctesting.FirstChannelID).String() + suite.Require().Equal(expected, res.EscrowAddress) + } else { + suite.Require().Error(err) + } + }) + } +} diff --git a/modules/apps/transfer/keeper/keeper.go b/modules/apps/transfer/keeper/keeper.go index 46e3c51aa2a..d2b8c70778f 100644 --- a/modules/apps/transfer/keeper/keeper.go +++ b/modules/apps/transfer/keeper/keeper.go @@ -35,7 +35,6 @@ func NewKeeper( ics4Wrapper types.ICS4Wrapper, channelKeeper types.ChannelKeeper, portKeeper types.PortKeeper, authKeeper types.AccountKeeper, bankKeeper types.BankKeeper, scopedKeeper capabilitykeeper.ScopedKeeper, ) Keeper { - // ensure ibc transfer module account is set if addr := authKeeper.GetModuleAddress(types.ModuleName); addr == nil { panic("the IBC transfer module account has not been set") diff --git a/modules/apps/transfer/keeper/mbt_relay_test.go b/modules/apps/transfer/keeper/mbt_relay_test.go index d4d338bf73a..59e4869e54e 100644 --- a/modules/apps/transfer/keeper/mbt_relay_test.go +++ b/modules/apps/transfer/keeper/mbt_relay_test.go @@ -280,7 +280,7 @@ func (suite *KeeperTestSuite) TestModelBasedRelay() { panic(fmt.Errorf("Failed to read model-based test files: %w", err)) } for _, file_info := range files { - var tlaTestCases = []TlaOnRecvPacketTestCase{} + tlaTestCases := []TlaOnRecvPacketTestCase{} if !strings.HasSuffix(file_info.Name(), ".json") { continue } diff --git a/modules/apps/transfer/keeper/migrations.go b/modules/apps/transfer/keeper/migrations.go new file mode 100644 index 00000000000..af83ebb0850 --- /dev/null +++ b/modules/apps/transfer/keeper/migrations.go @@ -0,0 +1,58 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v3/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} +} + +// MigrateTraces migrates the DenomTraces to the correct format, accounting for slashes in the BaseDenom. +func (m Migrator) MigrateTraces(ctx sdk.Context) error { + // list of traces that must replace the old traces in store + var newTraces []types.DenomTrace + m.keeper.IterateDenomTraces(ctx, + func(dt types.DenomTrace) (stop bool) { + // check if the new way of splitting FullDenom + // is the same as the current DenomTrace. + // If it isn't then store the new DenomTrace in the list of new traces. + newTrace := types.ParseDenomTrace(dt.GetFullDenomPath()) + err := newTrace.Validate() + if err != nil { + panic(err) + } + + if dt.IBCDenom() != newTrace.IBCDenom() { + // The new form of parsing will result in a token denomination change. + // A bank migration is required. A panic should occur to prevent the + // chain from using corrupted state. + panic(fmt.Sprintf("migration will result in corrupted state. Previous IBC token (%s) requires a bank migration. Expected denom trace (%s)", dt, newTrace)) + } + + if !equalTraces(newTrace, dt) { + newTraces = append(newTraces, newTrace) + } + return false + }) + + // replace the outdated traces with the new trace information + for _, nt := range newTraces { + m.keeper.SetDenomTrace(ctx, nt) + } + return nil +} + +func equalTraces(dtA, dtB types.DenomTrace) bool { + return dtA.BaseDenom == dtB.BaseDenom && dtA.Path == dtB.Path +} diff --git a/modules/apps/transfer/keeper/migrations_test.go b/modules/apps/transfer/keeper/migrations_test.go new file mode 100644 index 00000000000..3f1d92661a6 --- /dev/null +++ b/modules/apps/transfer/keeper/migrations_test.go @@ -0,0 +1,121 @@ +package keeper_test + +import ( + "fmt" + + transferkeeper "github.com/cosmos/ibc-go/v3/modules/apps/transfer/keeper" + transfertypes "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" +) + +func (suite *KeeperTestSuite) TestMigratorMigrateTraces() { + testCases := []struct { + msg string + malleate func() + expectedTraces transfertypes.Traces + }{ + { + "success: two slashes in base denom", + func() { + suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace( + suite.chainA.GetContext(), + transfertypes.DenomTrace{ + BaseDenom: "pool/1", Path: "transfer/channel-0/gamm", + }) + }, + transfertypes.Traces{ + { + BaseDenom: "gamm/pool/1", Path: "transfer/channel-0", + }, + }, + }, + { + "success: one slash in base denom", + func() { + suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace( + suite.chainA.GetContext(), + transfertypes.DenomTrace{ + BaseDenom: "0x85bcBCd7e79Ec36f4fBBDc54F90C643d921151AA", Path: "transfer/channel-149/erc", + }) + }, + transfertypes.Traces{ + { + BaseDenom: "erc/0x85bcBCd7e79Ec36f4fBBDc54F90C643d921151AA", Path: "transfer/channel-149", + }, + }, + }, + { + "success: multiple slashes in a row in base denom", + func() { + suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace( + suite.chainA.GetContext(), + transfertypes.DenomTrace{ + BaseDenom: "1", Path: "transfer/channel-5/gamm//pool", + }) + }, + transfertypes.Traces{ + { + BaseDenom: "gamm//pool/1", Path: "transfer/channel-5", + }, + }, + }, + { + "success: multihop base denom", + func() { + suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace( + suite.chainA.GetContext(), + transfertypes.DenomTrace{ + BaseDenom: "transfer/channel-1/uatom", Path: "transfer/channel-0", + }) + }, + transfertypes.Traces{ + { + BaseDenom: "uatom", Path: "transfer/channel-0/transfer/channel-1", + }, + }, + }, + { + "success: non-standard port", + func() { + suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace( + suite.chainA.GetContext(), + transfertypes.DenomTrace{ + BaseDenom: "customport/channel-7/uatom", Path: "transfer/channel-0/transfer/channel-1", + }) + }, + transfertypes.Traces{ + { + BaseDenom: "uatom", Path: "transfer/channel-0/transfer/channel-1/customport/channel-7", + }, + }, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() // explicitly set up denom traces + + migrator := transferkeeper.NewMigrator(suite.chainA.GetSimApp().TransferKeeper) + err := migrator.MigrateTraces(suite.chainA.GetContext()) + suite.Require().NoError(err) + + traces := suite.chainA.GetSimApp().TransferKeeper.GetAllDenomTraces(suite.chainA.GetContext()) + suite.Require().Equal(tc.expectedTraces, traces) + }) + } +} + +func (suite *KeeperTestSuite) TestMigratorMigrateTracesCorruptionDetection() { + // IBCDenom() previously would return "customport/channel-0/uatom", but now should return ibc/{hash} + corruptedDenomTrace := transfertypes.DenomTrace{ + BaseDenom: "customport/channel-0/uatom", + Path: "", + } + suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace(suite.chainA.GetContext(), corruptedDenomTrace) + + migrator := transferkeeper.NewMigrator(suite.chainA.GetSimApp().TransferKeeper) + suite.Panics(func() { + migrator.MigrateTraces(suite.chainA.GetContext()) + }) +} diff --git a/modules/apps/transfer/keeper/msg_server.go b/modules/apps/transfer/keeper/msg_server.go index 5d8e5682200..8993bf734d3 100644 --- a/modules/apps/transfer/keeper/msg_server.go +++ b/modules/apps/transfer/keeper/msg_server.go @@ -10,8 +10,6 @@ import ( var _ types.MsgServer = Keeper{} -// See createOutgoingPacket in spec:https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#packet-relay - // Transfer defines a rpc handler method for MsgTransfer. func (k Keeper) Transfer(goCtx context.Context, msg *types.MsgTransfer) (*types.MsgTransferResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) @@ -20,9 +18,11 @@ func (k Keeper) Transfer(goCtx context.Context, msg *types.MsgTransfer) (*types. if err != nil { return nil, err } - if err := k.SendTransfer( + + sequence, err := k.sendTransfer( ctx, msg.SourcePort, msg.SourceChannel, msg.Token, sender, msg.Receiver, msg.TimeoutHeight, msg.TimeoutTimestamp, - ); err != nil { + msg.Memo) + if err != nil { return nil, err } @@ -40,5 +40,5 @@ func (k Keeper) Transfer(goCtx context.Context, msg *types.MsgTransfer) (*types. ), }) - return &types.MsgTransferResponse{}, nil + return &types.MsgTransferResponse{Sequence: sequence}, 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 00000000000..ef94eacf843 --- /dev/null +++ b/modules/apps/transfer/keeper/msg_server_test.go @@ -0,0 +1,96 @@ +package keeper_test + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" +) + +func (suite *KeeperTestSuite) TestMsgTransfer() { + var msg *types.MsgTransfer + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "success", + func() {}, + true, + }, + { + "bank send enabled for denom", + func() { + suite.chainA.GetSimApp().BankKeeper.SetParams(suite.chainA.GetContext(), + banktypes.Params{ + SendEnabled: []*banktypes.SendEnabled{{Denom: sdk.DefaultBondDenom, Enabled: true}}, + }, + ) + }, + true, + }, + { + "invalid sender", + func() { + msg.Sender = "address" + }, + false, + }, + { + "sender is a blocked address", + func() { + msg.Sender = suite.chainA.GetSimApp().AccountKeeper.GetModuleAddress(types.ModuleName).String() + }, + false, + }, + { + "bank send disabled for denom", + func() { + suite.chainA.GetSimApp().BankKeeper.SetParams(suite.chainA.GetContext(), + banktypes.Params{ + SendEnabled: []*banktypes.SendEnabled{{Denom: sdk.DefaultBondDenom, Enabled: false}}, + }, + ) + }, + false, + }, + { + "channel does not exist", + func() { + msg.SourceChannel = "channel-100" + }, + false, + }, + } + + for _, tc := range testCases { + suite.SetupTest() + + path := NewTransferPath(suite.chainA, suite.chainB) + suite.coordinator.Setup(path) + + coin := sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)) + msg = types.NewMsgTransfer( + path.EndpointA.ChannelConfig.PortID, + path.EndpointA.ChannelID, + coin, suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String(), + suite.chainB.GetTimeoutHeight(), 0, // only use timeout height + ) + msg.Memo = "memo" + + tc.malleate() + + res, err := suite.chainA.GetSimApp().TransferKeeper.Transfer(sdk.WrapSDKContext(suite.chainA.GetContext()), msg) + + if tc.expPass { + suite.Require().NotEqual(res.Sequence, uint64(0)) + suite.Require().NoError(err) + suite.Require().NotNil(res) + } else { + suite.Require().Error(err) + suite.Require().Nil(res) + } + } +} diff --git a/modules/apps/transfer/keeper/relay.go b/modules/apps/transfer/keeper/relay.go index 3c3a5aa6690..6fcd135c86a 100644 --- a/modules/apps/transfer/keeper/relay.go +++ b/modules/apps/transfer/keeper/relay.go @@ -48,6 +48,8 @@ import ( // 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' +// +// Note: An IBC Transfer must be initiated using a MsgTransfer via the Transfer rpc handler func (k Keeper) SendTransfer( ctx sdk.Context, sourcePort, @@ -58,14 +60,47 @@ func (k Keeper) SendTransfer( timeoutHeight clienttypes.Height, timeoutTimestamp uint64, ) error { + _, err := k.sendTransfer( + ctx, + sourcePort, + sourceChannel, + token, + sender, + receiver, + timeoutHeight, + timeoutTimestamp, + "", + ) + return err +} +// sendTransfer handles transfer sending logic. +func (k Keeper) sendTransfer( + ctx sdk.Context, + sourcePort, + sourceChannel string, + token sdk.Coin, + sender sdk.AccAddress, + receiver string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + memo string, +) (uint64, error) { if !k.GetSendEnabled(ctx) { - return types.ErrSendDisabled + return 0, types.ErrSendDisabled + } + + if !k.bankKeeper.IsSendEnabledCoin(ctx, token) { + return 0, sdkerrors.Wrapf(types.ErrSendDisabled, "%s transfers are currently disabled", token.Denom) + } + + if k.bankKeeper.BlockedAddr(sender) { + return 0, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to send funds", sender) } sourceChannelEnd, found := k.channelKeeper.GetChannel(ctx, sourcePort, sourceChannel) if !found { - return sdkerrors.Wrapf(channeltypes.ErrChannelNotFound, "port ID (%s) channel ID (%s)", sourcePort, sourceChannel) + return 0, sdkerrors.Wrapf(channeltypes.ErrChannelNotFound, "port ID (%s) channel ID (%s)", sourcePort, sourceChannel) } destinationPort := sourceChannelEnd.GetCounterparty().GetPortID() @@ -74,7 +109,7 @@ func (k Keeper) SendTransfer( // get the next sequence sequence, found := k.channelKeeper.GetNextSequenceSend(ctx, sourcePort, sourceChannel) if !found { - return sdkerrors.Wrapf( + return 0, sdkerrors.Wrapf( channeltypes.ErrSequenceSendNotFound, "source port: %s, source channel: %s", sourcePort, sourceChannel, ) @@ -84,7 +119,7 @@ func (k Keeper) SendTransfer( // See spec for this logic: https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#packet-relay channelCap, ok := k.scopedKeeper.GetCapability(ctx, host.ChannelCapabilityPath(sourcePort, sourceChannel)) if !ok { - return sdkerrors.Wrap(channeltypes.ErrChannelCapabilityNotFound, "module does not own channel capability") + return 0, sdkerrors.Wrap(channeltypes.ErrChannelCapabilityNotFound, "module does not own channel capability") } // NOTE: denomination and hex hash correctness checked during msg.ValidateBasic @@ -97,7 +132,7 @@ func (k Keeper) SendTransfer( if strings.HasPrefix(token.Denom, "ibc/") { fullDenomPath, err = k.DenomPathFromHash(ctx, token.Denom) if err != nil { - return err + return 0, err } } @@ -120,7 +155,7 @@ func (k Keeper) SendTransfer( if err := k.bankKeeper.SendCoins( ctx, sender, escrowAddress, sdk.NewCoins(token), ); err != nil { - return err + return 0, err } } else { @@ -130,7 +165,7 @@ func (k Keeper) SendTransfer( if err := k.bankKeeper.SendCoinsFromAccountToModule( ctx, sender, types.ModuleName, sdk.NewCoins(token), ); err != nil { - return err + return 0, err } if err := k.bankKeeper.BurnCoins( @@ -146,6 +181,7 @@ func (k Keeper) SendTransfer( packetData := types.NewFungibleTokenPacketData( fullDenomPath, token.Amount.String(), sender.String(), receiver, ) + packetData.Memo = memo packet := channeltypes.NewPacket( packetData.GetBytes(), @@ -159,7 +195,7 @@ func (k Keeper) SendTransfer( ) if err := k.ics4Wrapper.SendPacket(ctx, channelCap, packet); err != nil { - return err + return 0, err } defer func() { @@ -178,7 +214,7 @@ func (k Keeper) SendTransfer( ) }() - return nil + return sequence, nil } // OnRecvPacket processes a cross chain fungible token transfer. If the diff --git a/modules/apps/transfer/keeper/relay_test.go b/modules/apps/transfer/keeper/relay_test.go index ce34f316669..a10d4020a9b 100644 --- a/modules/apps/transfer/keeper/relay_test.go +++ b/modules/apps/transfer/keeper/relay_test.go @@ -19,6 +19,7 @@ func (suite *KeeperTestSuite) TestSendTransfer() { var ( amount sdk.Coin path *ibctesting.Path + sender sdk.AccAddress err error ) @@ -28,25 +29,32 @@ func (suite *KeeperTestSuite) TestSendTransfer() { sendFromSource bool expPass bool }{ - {"successful transfer from source chain", + { + "successful transfer from source chain", func() { suite.coordinator.CreateTransferChannels(path) amount = sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)) - }, true, true}, - {"successful transfer with coin from counterparty chain", + }, true, true, + }, + { + "successful transfer with coin from counterparty chain", func() { // send coin from chainA back to chainB suite.coordinator.CreateTransferChannels(path) amount = types.GetTransferCoin(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, sdk.DefaultBondDenom, sdk.NewInt(100)) - }, false, true}, - {"source channel not found", + }, false, true, + }, + { + "source channel not found", func() { // channel references wrong ID suite.coordinator.CreateTransferChannels(path) path.EndpointA.ChannelID = ibctesting.InvalidID amount = sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)) - }, true, false}, - {"next seq send not found", + }, true, false, + }, + { + "next seq send not found", func() { path.EndpointA.ChannelID = "channel-0" path.EndpointB.ChannelID = "channel-0" @@ -58,22 +66,35 @@ func (suite *KeeperTestSuite) TestSendTransfer() { ) suite.chainA.CreateChannelCapability(suite.chainA.GetSimApp().ScopedIBCMockKeeper, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) amount = sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)) - }, true, false}, - + }, true, false, + }, + { + "transfer failed - sender account is blocked", + func() { + suite.coordinator.CreateTransferChannels(path) + amount = sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)) + sender = suite.chainA.GetSimApp().AccountKeeper.GetModuleAddress(types.ModuleName) + }, true, false, + }, // createOutgoingPacket tests // - source chain - {"send coin failed", + { + "send coin failed", func() { suite.coordinator.CreateTransferChannels(path) amount = sdk.NewCoin("randomdenom", sdk.NewInt(100)) - }, true, false}, + }, true, false, + }, // - receiving chain - {"send from module account failed", + { + "send from module account failed", func() { suite.coordinator.CreateTransferChannels(path) amount = types.GetTransferCoin(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, " randomdenom", sdk.NewInt(100)) - }, false, false}, - {"channel capability not found", + }, false, false, + }, + { + "channel capability not found", func() { suite.coordinator.CreateTransferChannels(path) cap := suite.chainA.GetChannelCapability(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) @@ -81,7 +102,8 @@ func (suite *KeeperTestSuite) TestSendTransfer() { // Release channel capability suite.chainA.GetSimApp().ScopedTransferKeeper.ReleaseCapability(suite.chainA.GetContext(), cap) amount = sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)) - }, true, false}, + }, true, false, + }, } for _, tc := range testCases { @@ -91,6 +113,7 @@ func (suite *KeeperTestSuite) TestSendTransfer() { suite.SetupTest() // reset path = NewTransferPath(suite.chainA, suite.chainB) suite.coordinator.SetupConnections(path) + sender = suite.chainA.SenderAccount.GetAddress() tc.malleate() @@ -118,7 +141,7 @@ func (suite *KeeperTestSuite) TestSendTransfer() { err = suite.chainA.GetSimApp().TransferKeeper.SendTransfer( suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, amount, - suite.chainA.SenderAccount.GetAddress(), suite.chainB.SenderAccount.GetAddress().String(), clienttypes.NewHeight(0, 110), 0, + sender, suite.chainB.SenderAccount.GetAddress().String(), suite.chainB.GetTimeoutHeight(), 0, ) if tc.expPass { @@ -139,6 +162,7 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() { trace types.DenomTrace amount sdk.Int receiver string + memo string ) testCases := []struct { @@ -148,7 +172,13 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() { expPass bool }{ {"success receive on source chain", func() {}, true, true}, + {"success receive on source chain with memo", func() { + memo = "memo" + }, true, true}, {"success receive with coin from another chain as source", func() {}, false, true}, + {"success receive with coin from another chain as source with memo", func() { + memo = "memo" + }, false, true}, {"empty coin", func() { trace = types.DenomTrace{} amount = sdk.ZeroInt() @@ -189,6 +219,7 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() { suite.coordinator.Setup(path) receiver = suite.chainB.SenderAccount.GetAddress().String() // must be explicitly changed in malleate + memo = "" // can be explicitly changed in malleate amount = sdk.NewInt(100) // must be explicitly changed in malleate seq := uint64(1) @@ -215,12 +246,14 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() { // send coin from chainA to chainB transferMsg := types.NewMsgTransfer(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, sdk.NewCoin(trace.IBCDenom(), amount), suite.chainA.SenderAccount.GetAddress().String(), receiver, clienttypes.NewHeight(0, 110), 0) + transferMsg.Memo = memo _, err := suite.chainA.SendMsgs(transferMsg) suite.Require().NoError(err) // message committed tc.malleate() data := types.NewFungibleTokenPacketData(trace.GetFullDenomPath(), amount.String(), suite.chainA.SenderAccount.GetAddress().String(), receiver) + data.Memo = memo packet := channeltypes.NewPacket(data.GetBytes(), seq, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.NewHeight(0, 100), 0) err = suite.chainB.GetSimApp().TransferKeeper.OnRecvPacket(suite.chainB.GetContext(), packet, data) @@ -264,18 +297,22 @@ func (suite *KeeperTestSuite) TestOnAcknowledgementPacket() { suite.Require().NoError(simapp.FundAccount(suite.chainA.GetSimApp(), suite.chainA.GetContext(), escrow, sdk.NewCoins(coin))) }, false, true}, - {"unsuccessful refund from source", failedAck, + { + "unsuccessful refund from source", failedAck, func() { trace = types.ParseDenomTrace(sdk.DefaultBondDenom) - }, false, false}, - {"successful refund from with coin from external chain", failedAck, + }, false, false, + }, + { + "successful refund from with coin from external chain", failedAck, func() { escrow := types.GetEscrowAddress(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) trace = types.ParseDenomTrace(types.GetPrefixedDenom(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, sdk.DefaultBondDenom)) coin := sdk.NewCoin(trace.IBCDenom(), amount) suite.Require().NoError(simapp.FundAccount(suite.chainA.GetSimApp(), suite.chainA.GetContext(), escrow, sdk.NewCoins(coin))) - }, false, true}, + }, false, true, + }, } for _, tc := range testCases { @@ -330,36 +367,46 @@ func (suite *KeeperTestSuite) TestOnTimeoutPacket() { malleate func() expPass bool }{ - {"successful timeout from sender as source chain", + { + "successful timeout from sender as source chain", func() { escrow := types.GetEscrowAddress(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) trace = types.ParseDenomTrace(sdk.DefaultBondDenom) coin := sdk.NewCoin(trace.IBCDenom(), amount) suite.Require().NoError(simapp.FundAccount(suite.chainA.GetSimApp(), suite.chainA.GetContext(), escrow, sdk.NewCoins(coin))) - }, true}, - {"successful timeout from external chain", + }, true, + }, + { + "successful timeout from external chain", func() { escrow := types.GetEscrowAddress(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) trace = types.ParseDenomTrace(types.GetPrefixedDenom(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, sdk.DefaultBondDenom)) coin := sdk.NewCoin(trace.IBCDenom(), amount) suite.Require().NoError(simapp.FundAccount(suite.chainA.GetSimApp(), suite.chainA.GetContext(), escrow, sdk.NewCoins(coin))) - }, true}, - {"no balance for coin denom", + }, true, + }, + { + "no balance for coin denom", func() { trace = types.ParseDenomTrace("bitcoin") - }, false}, - {"unescrow failed", + }, false, + }, + { + "unescrow failed", func() { trace = types.ParseDenomTrace(sdk.DefaultBondDenom) - }, false}, - {"mint failed", + }, false, + }, + { + "mint failed", func() { trace = types.ParseDenomTrace(types.GetPrefixedDenom(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, sdk.DefaultBondDenom)) amount = sdk.OneInt() sender = "invalid address" - }, false}, + }, false, + }, } for _, tc := range testCases { diff --git a/modules/apps/transfer/module.go b/modules/apps/transfer/module.go index 0daa6dc3bf0..8c0fab0b091 100644 --- a/modules/apps/transfer/module.go +++ b/modules/apps/transfer/module.go @@ -120,6 +120,11 @@ func (am AppModule) LegacyQuerierHandler(*codec.LegacyAmino) sdk.Querier { 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, 1, m.MigrateTraces); err != nil { + panic(fmt.Sprintf("failed to migrate transfer app from version 1 to 2: %v", err)) + } } // InitGenesis performs genesis initialization for the ibc-transfer module. It returns @@ -139,7 +144,7 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw } // ConsensusVersion implements AppModule/ConsensusVersion. -func (AppModule) ConsensusVersion() uint64 { return 1 } +func (AppModule) ConsensusVersion() uint64 { return 2 } // BeginBlock implements the AppModule interface func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { diff --git a/modules/apps/transfer/simulation/genesis_test.go b/modules/apps/transfer/simulation/genesis_test.go index c869d1c7b25..f2aa439b3ae 100644 --- a/modules/apps/transfer/simulation/genesis_test.go +++ b/modules/apps/transfer/simulation/genesis_test.go @@ -43,7 +43,6 @@ func TestRandomizedGenState(t *testing.T) { require.True(t, ibcTransferGenesis.Params.SendEnabled) require.True(t, ibcTransferGenesis.Params.ReceiveEnabled) require.Len(t, ibcTransferGenesis.DenomTraces, 0) - } // TestRandomizedGenState tests abnormal scenarios of applying RandomizedGenState. diff --git a/modules/apps/transfer/spec/04_messages.md b/modules/apps/transfer/spec/04_messages.md index 611e2423da7..143c9c7f037 100644 --- a/modules/apps/transfer/spec/04_messages.md +++ b/modules/apps/transfer/spec/04_messages.md @@ -17,6 +17,7 @@ type MsgTransfer struct { Receiver string TimeoutHeight ibcexported.Height TimeoutTimestamp uint64 + Memo string } ``` diff --git a/modules/apps/transfer/spec/05_events.md b/modules/apps/transfer/spec/05_events.md index 51b49da4602..f5cfc443a81 100644 --- a/modules/apps/transfer/spec/05_events.md +++ b/modules/apps/transfer/spec/05_events.md @@ -22,6 +22,7 @@ order: 5 | fungible_token_packet | denom | {denom} | | fungible_token_packet | amount | {amount} | | fungible_token_packet | success | {ackSuccess} | +| fungible_token_packet | memo | {memo} | | denomination_trace | trace_hash | {hex_hash} | ## OnAcknowledgePacket callback @@ -32,6 +33,8 @@ order: 5 | 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 @@ -42,3 +45,4 @@ order: 5 | fungible_token_packet | refund_receiver | {receiver} | | fungible_token_packet | denom | {denom} | | fungible_token_packet | amount | {amount} | +| fungible_token_packet | memo | {memo} | diff --git a/modules/apps/transfer/spec/06_metrics.md b/modules/apps/transfer/spec/06_metrics.md index 3d2ce5000e6..7d83b08eb13 100644 --- a/modules/apps/transfer/spec/06_metrics.md +++ b/modules/apps/transfer/spec/06_metrics.md @@ -4,7 +4,7 @@ order: 6 # Metrics -The transfer IBC application module exposes the following set of [metrics](https://github.com/cosmos/cosmos-sdk/blob/master/docs/core/telemetry.md). +The IBC transfer application module exposes the following set of [metrics](https://github.com/cosmos/cosmos-sdk/blob/main/docs/docs/core/09-telemetry.md). | Metric | Description | Unit | Type | |:--------------------------------|:------------------------------------------------------------------------------------------|:----------------|:--------| diff --git a/modules/apps/transfer/spec/07_params.md b/modules/apps/transfer/spec/07_params.md index 2605f06849c..7e1fa087602 100644 --- a/modules/apps/transfer/spec/07_params.md +++ b/modules/apps/transfer/spec/07_params.md @@ -23,4 +23,5 @@ To prevent a single token from being transferred from the chain, set the `SendEn 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, 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`. +To prevent a single token from being transferred to the chain, set the `ReceiveEnabled` parameter to `true` and +then, 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`. diff --git a/modules/apps/transfer/types/ack_test.go b/modules/apps/transfer/types/ack_test.go index 4f4c3a874d7..ffc09da981e 100644 --- a/modules/apps/transfer/types/ack_test.go +++ b/modules/apps/transfer/types/ack_test.go @@ -97,5 +97,4 @@ func (suite *TypesTestSuite) TestAcknowledgementError() { suite.Require().Equal(ack, ackSameABCICode) suite.Require().NotEqual(ack, ackDifferentABCICode) - } diff --git a/modules/apps/transfer/types/codec.go b/modules/apps/transfer/types/codec.go index 24ad7e5a902..3067cf8dc9d 100644 --- a/modules/apps/transfer/types/codec.go +++ b/modules/apps/transfer/types/codec.go @@ -1,10 +1,14 @@ package types import ( + "bytes" + "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/gogo/protobuf/jsonpb" + "github.com/gogo/protobuf/proto" ) // RegisterLegacyAminoCodec registers the necessary x/ibc transfer interfaces and concrete types @@ -39,3 +43,32 @@ func init() { RegisterLegacyAminoCodec(amino) amino.Seal() } + +// mustProtoMarshalJSON provides an auxiliary function to return Proto3 JSON encoded +// bytes of a message. +// NOTE: Copied from https://github.com/cosmos/cosmos-sdk/blob/971c542453e0972ef1dfc5a80159ad5049c7211c/codec/json.go +// and modified in order to allow `EmitDefaults` to be set to false for ics20 packet marshalling. +// This allows for the introduction of the memo field to be backwards compatible. +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/modules/apps/transfer/types/codec_test.go b/modules/apps/transfer/types/codec_test.go new file mode 100644 index 00000000000..b724cb2c6e2 --- /dev/null +++ b/modules/apps/transfer/types/codec_test.go @@ -0,0 +1,26 @@ +package types_test + +import ( + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" +) + +// 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()) + packetData.Memo = 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) +} diff --git a/modules/apps/transfer/types/coin.go b/modules/apps/transfer/types/coin.go index a3491e2bf09..d6329541103 100644 --- a/modules/apps/transfer/types/coin.go +++ b/modules/apps/transfer/types/coin.go @@ -27,7 +27,6 @@ func ReceiverChainIsSource(sourcePort, sourceChannel, denom string) bool { voucherPrefix := GetDenomPrefix(sourcePort, sourceChannel) return strings.HasPrefix(denom, voucherPrefix) - } // GetDenomPrefix returns the receiving denomination prefix diff --git a/modules/apps/transfer/types/events.go b/modules/apps/transfer/types/events.go index a3ed5b413c7..89964a8a9a4 100644 --- a/modules/apps/transfer/types/events.go +++ b/modules/apps/transfer/types/events.go @@ -18,4 +18,5 @@ const ( AttributeKeyAck = "acknowledgement" AttributeKeyAckError = "error" AttributeKeyTraceHash = "trace_hash" + AttributeKeyMemo = "memo" ) diff --git a/modules/apps/transfer/types/expected_keepers.go b/modules/apps/transfer/types/expected_keepers.go index 22ad54b9e62..9106366b198 100644 --- a/modules/apps/transfer/types/expected_keepers.go +++ b/modules/apps/transfer/types/expected_keepers.go @@ -24,6 +24,7 @@ type BankKeeper interface { SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error BlockedAddr(addr sdk.AccAddress) bool + IsSendEnabledCoin(ctx sdk.Context, coin sdk.Coin) bool } // ICS4Wrapper defines the expected ICS4Wrapper for middleware diff --git a/modules/apps/transfer/types/msgs.go b/modules/apps/transfer/types/msgs.go index dab9fb21d46..13c0b8b419e 100644 --- a/modules/apps/transfer/types/msgs.go +++ b/modules/apps/transfer/types/msgs.go @@ -16,6 +16,7 @@ const ( ) // NewMsgTransfer creates a new MsgTransfer instance +// //nolint:interfacer func NewMsgTransfer( sourcePort, sourceChannel string, diff --git a/modules/apps/transfer/types/packet.go b/modules/apps/transfer/types/packet.go index a7384c47486..59d22d03b97 100644 --- a/modules/apps/transfer/types/packet.go +++ b/modules/apps/transfer/types/packet.go @@ -56,5 +56,5 @@ func (ftpd FungibleTokenPacketData) ValidateBasic() error { // GetBytes is a helper for serialising func (ftpd FungibleTokenPacketData) GetBytes() []byte { - return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&ftpd)) + return sdk.MustSortJSON(mustProtoMarshalJSON(&ftpd)) } diff --git a/modules/apps/transfer/types/packet.pb.go b/modules/apps/transfer/types/packet.pb.go index 3ebd1106f66..9a2067facf2 100644 --- a/modules/apps/transfer/types/packet.pb.go +++ b/modules/apps/transfer/types/packet.pb.go @@ -34,6 +34,8 @@ type FungibleTokenPacketData struct { 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{} } @@ -97,6 +99,13 @@ func (m *FungibleTokenPacketData) GetReceiver() string { return "" } +func (m *FungibleTokenPacketData) GetMemo() string { + if m != nil { + return m.Memo + } + return "" +} + func init() { proto.RegisterType((*FungibleTokenPacketData)(nil), "ibc.applications.transfer.v2.FungibleTokenPacketData") } @@ -106,23 +115,23 @@ func init() { } var fileDescriptor_653ca2ce9a5ca313 = []byte{ - // 242 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x8f, 0xbd, 0x4a, 0x04, 0x31, - 0x14, 0x46, 0x27, 0xfe, 0x2c, 0x9a, 0x72, 0x10, 0x1d, 0x44, 0x82, 0x58, 0x69, 0x61, 0x02, 0xbb, - 0x85, 0xbd, 0x88, 0xb5, 0x8a, 0x95, 0x5d, 0x92, 0xb9, 0x8e, 0x61, 0x27, 0xb9, 0x21, 0xc9, 0x0c, - 0x88, 0x2f, 0xe1, 0x63, 0x59, 0x6e, 0x69, 0x29, 0x33, 0x2f, 0x22, 0x9b, 0xd1, 0x65, 0xcb, 0x73, - 0xee, 0x77, 0x8b, 0x43, 0xaf, 0x8c, 0xd2, 0x42, 0x7a, 0xdf, 0x1a, 0x2d, 0x93, 0x41, 0x17, 0x45, - 0x0a, 0xd2, 0xc5, 0x57, 0x08, 0xa2, 0x9f, 0x0b, 0x2f, 0xf5, 0x12, 0x12, 0xf7, 0x01, 0x13, 0x96, - 0x67, 0x46, 0x69, 0xbe, 0x3d, 0xe5, 0xff, 0x53, 0xde, 0xcf, 0x2f, 0x3e, 0xe8, 0xc9, 0x7d, 0xe7, - 0x1a, 0xa3, 0x5a, 0x78, 0xc6, 0x25, 0xb8, 0x87, 0xfc, 0x7a, 0x27, 0x93, 0x2c, 0x8f, 0xe8, 0x7e, - 0x0d, 0x0e, 0x6d, 0x45, 0xce, 0xc9, 0xe5, 0xe1, 0xd3, 0x04, 0xe5, 0x31, 0x9d, 0x49, 0x8b, 0x9d, - 0x4b, 0xd5, 0x4e, 0xd6, 0x7f, 0xb4, 0xf6, 0x11, 0x5c, 0x0d, 0xa1, 0xda, 0x9d, 0xfc, 0x44, 0xe5, - 0x29, 0x3d, 0x08, 0xa0, 0xc1, 0xf4, 0x10, 0xaa, 0xbd, 0x7c, 0xd9, 0xf0, 0xed, 0xe3, 0xd7, 0xc0, - 0xc8, 0x6a, 0x60, 0xe4, 0x67, 0x60, 0xe4, 0x73, 0x64, 0xc5, 0x6a, 0x64, 0xc5, 0xf7, 0xc8, 0x8a, - 0x97, 0x9b, 0xc6, 0xa4, 0xb7, 0x4e, 0x71, 0x8d, 0x56, 0x68, 0x8c, 0x16, 0xa3, 0x30, 0x4a, 0x5f, - 0x37, 0x28, 0xfa, 0x85, 0xb0, 0x58, 0x77, 0x2d, 0xc4, 0x75, 0xff, 0x56, 0x77, 0x7a, 0xf7, 0x10, - 0xd5, 0x2c, 0x47, 0x2f, 0x7e, 0x03, 0x00, 0x00, 0xff, 0xff, 0x82, 0x62, 0x02, 0xc6, 0x21, 0x01, - 0x00, 0x00, + // 254 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x90, 0xb1, 0x4a, 0x34, 0x31, + 0x14, 0x46, 0x27, 0xff, 0xbf, 0xbb, 0x68, 0xca, 0x20, 0x3a, 0x88, 0x04, 0xb1, 0xd2, 0xc2, 0x09, + 0xec, 0x16, 0xf6, 0x22, 0xd6, 0x2a, 0x56, 0x76, 0x49, 0xe6, 0x3a, 0x86, 0x9d, 0xe4, 0x86, 0x24, + 0x33, 0xe0, 0x53, 0xe8, 0x63, 0x59, 0x6e, 0x69, 0x29, 0x33, 0x2f, 0x22, 0x9b, 0x51, 0xd9, 0x2e, + 0xe7, 0xe4, 0xbb, 0xcd, 0xa1, 0x17, 0x46, 0x69, 0x21, 0xbd, 0x6f, 0x8d, 0x96, 0xc9, 0xa0, 0x8b, + 0x22, 0x05, 0xe9, 0xe2, 0x33, 0x04, 0xd1, 0x2f, 0x85, 0x97, 0x7a, 0x0d, 0xa9, 0xf2, 0x01, 0x13, + 0xb2, 0x13, 0xa3, 0x74, 0xb5, 0x3b, 0xad, 0x7e, 0xa7, 0x55, 0xbf, 0x3c, 0x7b, 0x23, 0xf4, 0xe8, + 0xb6, 0x73, 0x8d, 0x51, 0x2d, 0x3c, 0xe2, 0x1a, 0xdc, 0x5d, 0xbe, 0xbd, 0x91, 0x49, 0xb2, 0x03, + 0x3a, 0xaf, 0xc1, 0xa1, 0x2d, 0xc9, 0x29, 0x39, 0xdf, 0x7f, 0x98, 0x80, 0x1d, 0xd2, 0x85, 0xb4, + 0xd8, 0xb9, 0x54, 0xfe, 0xcb, 0xfa, 0x87, 0xb6, 0x3e, 0x82, 0xab, 0x21, 0x94, 0xff, 0x27, 0x3f, + 0x11, 0x3b, 0xa6, 0x7b, 0x01, 0x34, 0x98, 0x1e, 0x42, 0x39, 0xcb, 0x3f, 0x7f, 0xcc, 0x18, 0x9d, + 0x59, 0xb0, 0x58, 0xce, 0xb3, 0xcf, 0xef, 0xeb, 0xfb, 0x8f, 0x81, 0x93, 0xcd, 0xc0, 0xc9, 0xd7, + 0xc0, 0xc9, 0xfb, 0xc8, 0x8b, 0xcd, 0xc8, 0x8b, 0xcf, 0x91, 0x17, 0x4f, 0x57, 0x8d, 0x49, 0x2f, + 0x9d, 0xaa, 0x34, 0x5a, 0xa1, 0x31, 0x5a, 0x8c, 0xc2, 0x28, 0x7d, 0xd9, 0xa0, 0xe8, 0x57, 0xc2, + 0x62, 0xdd, 0xb5, 0x10, 0xb7, 0x51, 0x76, 0x62, 0xa4, 0x57, 0x0f, 0x51, 0x2d, 0x72, 0x89, 0xd5, + 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, 0x8d, 0xaf, 0x01, 0xb0, 0x36, 0x01, 0x00, 0x00, } func (m *FungibleTokenPacketData) Marshal() (dAtA []byte, err error) { @@ -145,6 +154,13 @@ func (m *FungibleTokenPacketData) MarshalToSizedBuffer(dAtA []byte) (int, error) _ = 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) @@ -209,6 +225,10 @@ func (m *FungibleTokenPacketData) Size() (n int) { if l > 0 { n += 1 + l + sovPacket(uint64(l)) } + l = len(m.Memo) + if l > 0 { + n += 1 + l + sovPacket(uint64(l)) + } return n } @@ -375,6 +395,38 @@ func (m *FungibleTokenPacketData) Unmarshal(dAtA []byte) error { } 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:]) diff --git a/modules/apps/transfer/types/query.pb.go b/modules/apps/transfer/types/query.pb.go index e1206ab0e30..a137649a094 100644 --- a/modules/apps/transfer/types/query.pb.go +++ b/modules/apps/transfer/types/query.pb.go @@ -404,6 +404,107 @@ func (m *QueryDenomHashResponse) GetHash() string { 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 "" +} + func init() { proto.RegisterType((*QueryDenomTraceRequest)(nil), "ibc.applications.transfer.v1.QueryDenomTraceRequest") proto.RegisterType((*QueryDenomTraceResponse)(nil), "ibc.applications.transfer.v1.QueryDenomTraceResponse") @@ -413,6 +514,8 @@ func init() { proto.RegisterType((*QueryParamsResponse)(nil), "ibc.applications.transfer.v1.QueryParamsResponse") 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") } func init() { @@ -420,45 +523,52 @@ func init() { } var fileDescriptor_a638e2800a01538c = []byte{ - // 595 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x54, 0x4f, 0x6f, 0xd3, 0x30, - 0x1c, 0xad, 0x07, 0xab, 0x34, 0x17, 0x71, 0x30, 0x05, 0xaa, 0xa8, 0xca, 0xa6, 0xa8, 0x82, 0xd2, - 0x6d, 0x36, 0x69, 0x07, 0x5c, 0x38, 0x4d, 0x88, 0x3f, 0xb7, 0xad, 0x70, 0x82, 0x03, 0x72, 0x52, - 0x93, 0x46, 0x6a, 0xe3, 0x2c, 0x4e, 0x2b, 0x4d, 0x68, 0x17, 0x3e, 0x01, 0xd2, 0xbe, 0x02, 0x07, - 0x34, 0xf1, 0x21, 0x38, 0xee, 0x38, 0x89, 0x0b, 0x27, 0x40, 0x2d, 0x1f, 0x04, 0xc5, 0x76, 0xda, - 0x84, 0x56, 0xdd, 0x72, 0x73, 0xdd, 0xdf, 0xfb, 0xfd, 0xde, 0x7b, 0xbf, 0x17, 0xc3, 0xa6, 0xef, - 0xb8, 0x84, 0x86, 0xe1, 0xc0, 0x77, 0x69, 0xec, 0xf3, 0x40, 0x90, 0x38, 0xa2, 0x81, 0xf8, 0xc0, - 0x22, 0x32, 0xb6, 0xc9, 0xd1, 0x88, 0x45, 0xc7, 0x38, 0x8c, 0x78, 0xcc, 0x51, 0xdd, 0x77, 0x5c, - 0x9c, 0xad, 0xc4, 0x69, 0x25, 0x1e, 0xdb, 0x46, 0xd5, 0xe3, 0x1e, 0x97, 0x85, 0x24, 0x39, 0x29, - 0x8c, 0xd1, 0x72, 0xb9, 0x18, 0x72, 0x41, 0x1c, 0x2a, 0x98, 0x6a, 0x46, 0xc6, 0xb6, 0xc3, 0x62, - 0x6a, 0x93, 0x90, 0x7a, 0x7e, 0x20, 0x1b, 0xe9, 0xda, 0xed, 0x95, 0x4c, 0x66, 0xb3, 0x54, 0x71, - 0xdd, 0xe3, 0xdc, 0x1b, 0x30, 0x42, 0x43, 0x9f, 0xd0, 0x20, 0xe0, 0xb1, 0xa6, 0x24, 0xff, 0xb5, - 0x76, 0xe0, 0x9d, 0xc3, 0x64, 0xd8, 0x33, 0x16, 0xf0, 0xe1, 0x9b, 0x88, 0xba, 0xac, 0xcb, 0x8e, - 0x46, 0x4c, 0xc4, 0x08, 0xc1, 0xeb, 0x7d, 0x2a, 0xfa, 0x35, 0xb0, 0x05, 0x9a, 0x1b, 0x5d, 0x79, - 0xb6, 0x7a, 0xf0, 0xee, 0x42, 0xb5, 0x08, 0x79, 0x20, 0x18, 0x7a, 0x05, 0x2b, 0xbd, 0xe4, 0xf6, - 0x7d, 0x9c, 0x5c, 0x4b, 0x54, 0xa5, 0xdd, 0xc4, 0xab, 0x9c, 0xc0, 0x99, 0x36, 0xb0, 0x37, 0x3b, - 0x5b, 0x74, 0x61, 0x8a, 0x48, 0x49, 0x3d, 0x87, 0x70, 0xee, 0x86, 0x1e, 0x72, 0x0f, 0x2b, 0xeb, - 0x70, 0x62, 0x1d, 0x56, 0x7b, 0xd0, 0xd6, 0xe1, 0x03, 0xea, 0xa5, 0x82, 0xba, 0x19, 0xa4, 0xf5, - 0x1d, 0xc0, 0xda, 0xe2, 0x0c, 0x2d, 0xe5, 0x1d, 0xbc, 0x91, 0x91, 0x22, 0x6a, 0x60, 0xeb, 0x5a, - 0x11, 0x2d, 0xfb, 0x37, 0xcf, 0x7f, 0x6d, 0x96, 0xce, 0x7e, 0x6f, 0x96, 0x75, 0xdf, 0xca, 0x5c, - 0x9b, 0x40, 0x2f, 0x72, 0x0a, 0xd6, 0xa4, 0x82, 0xfb, 0x97, 0x2a, 0x50, 0xcc, 0x72, 0x12, 0xaa, - 0x10, 0x49, 0x05, 0x07, 0x34, 0xa2, 0xc3, 0xd4, 0x20, 0xeb, 0x35, 0xbc, 0x95, 0xbb, 0xd5, 0x92, - 0x9e, 0xc2, 0x72, 0x28, 0x6f, 0xb4, 0x67, 0x8d, 0xd5, 0x62, 0x34, 0x5a, 0x63, 0xac, 0x5d, 0x78, - 0x7b, 0x6e, 0xd6, 0x4b, 0x2a, 0xfa, 0xe9, 0x3a, 0xaa, 0x70, 0x7d, 0xbe, 0xee, 0x8d, 0xae, 0xfa, - 0x91, 0xcf, 0x94, 0x2a, 0xd7, 0x34, 0x96, 0x64, 0xaa, 0xfd, 0x65, 0x1d, 0xae, 0xcb, 0x72, 0xf4, - 0x0d, 0x40, 0x38, 0xb7, 0x11, 0xed, 0xad, 0xe6, 0xb8, 0x3c, 0xb6, 0xc6, 0xa3, 0x82, 0x28, 0xc5, - 0xcc, 0xb2, 0x3f, 0xfd, 0xf8, 0x7b, 0xba, 0xb6, 0x8d, 0x1e, 0x10, 0xfd, 0x6d, 0xe5, 0xbf, 0xa9, - 0x6c, 0x1e, 0xc8, 0xc7, 0x84, 0xf7, 0x09, 0xfa, 0x0a, 0x60, 0x25, 0x13, 0x1f, 0x54, 0x6c, 0x72, - 0xba, 0x31, 0xe3, 0x71, 0x51, 0x98, 0x66, 0xdc, 0x92, 0x8c, 0x1b, 0xc8, 0xba, 0x9c, 0x31, 0x3a, - 0x05, 0xb0, 0xac, 0x76, 0x8a, 0x1e, 0x5e, 0x61, 0x5c, 0x2e, 0x52, 0x86, 0x5d, 0x00, 0xa1, 0xb9, - 0x35, 0x24, 0x37, 0x13, 0xd5, 0x97, 0x73, 0x53, 0xb1, 0x42, 0x67, 0x00, 0x6e, 0xcc, 0x32, 0x82, - 0x3a, 0x57, 0xf5, 0x21, 0x13, 0x40, 0x63, 0xaf, 0x18, 0x48, 0xd3, 0x6b, 0x4b, 0x7a, 0x3b, 0xa8, - 0xb5, 0xca, 0xba, 0x64, 0xc9, 0xc9, 0xb2, 0xa5, 0x85, 0x27, 0xfb, 0x87, 0xe7, 0x13, 0x13, 0x5c, - 0x4c, 0x4c, 0xf0, 0x67, 0x62, 0x82, 0xcf, 0x53, 0xb3, 0x74, 0x31, 0x35, 0x4b, 0x3f, 0xa7, 0x66, - 0xe9, 0xed, 0x13, 0xcf, 0x8f, 0xfb, 0x23, 0x07, 0xbb, 0x7c, 0x48, 0xf4, 0x23, 0xee, 0x3b, 0xee, - 0xae, 0xc7, 0xc9, 0xb8, 0x43, 0x86, 0xbc, 0x37, 0x1a, 0x30, 0xf1, 0xdf, 0x90, 0xf8, 0x38, 0x64, - 0xc2, 0x29, 0xcb, 0x27, 0xb8, 0xf3, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x16, 0x01, 0x88, 0xe2, 0x59, - 0x06, 0x00, 0x00, + // 715 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x55, 0xcd, 0x6e, 0xd3, 0x4c, + 0x14, 0x8d, 0xfb, 0x7d, 0x0d, 0xca, 0x0d, 0xed, 0x62, 0x28, 0xb4, 0x58, 0xc5, 0xad, 0xac, 0x02, + 0xa5, 0x3f, 0x1e, 0xd2, 0x16, 0xca, 0x82, 0x0d, 0xe5, 0xb7, 0x88, 0x45, 0x9b, 0xb2, 0x82, 0x45, + 0x35, 0xb6, 0x07, 0xc7, 0x52, 0xe2, 0x71, 0x3d, 0x4e, 0x50, 0x55, 0x65, 0xc3, 0x13, 0x20, 0xf5, + 0x25, 0x50, 0xc5, 0x43, 0xb0, 0xec, 0xb2, 0x12, 0x12, 0x62, 0x05, 0xa8, 0xe5, 0x35, 0x90, 0x90, + 0x67, 0x26, 0x89, 0x4d, 0xa3, 0x34, 0xde, 0x8d, 0x67, 0xee, 0xb9, 0xf7, 0x9c, 0x73, 0xef, 0x95, + 0x61, 0xde, 0xb7, 0x1d, 0x4c, 0xc2, 0xb0, 0xee, 0x3b, 0x24, 0xf6, 0x59, 0xc0, 0x71, 0x1c, 0x91, + 0x80, 0xbf, 0xa3, 0x11, 0x6e, 0x55, 0xf0, 0x5e, 0x93, 0x46, 0xfb, 0x56, 0x18, 0xb1, 0x98, 0xa1, + 0x69, 0xdf, 0x76, 0xac, 0x74, 0xa4, 0xd5, 0x89, 0xb4, 0x5a, 0x15, 0x7d, 0xc2, 0x63, 0x1e, 0x13, + 0x81, 0x38, 0x39, 0x49, 0x8c, 0xbe, 0xe0, 0x30, 0xde, 0x60, 0x1c, 0xdb, 0x84, 0x53, 0x99, 0x0c, + 0xb7, 0x2a, 0x36, 0x8d, 0x49, 0x05, 0x87, 0xc4, 0xf3, 0x03, 0x91, 0x48, 0xc5, 0x2e, 0x0e, 0x64, + 0xd2, 0xad, 0x25, 0x83, 0xa7, 0x3d, 0xc6, 0xbc, 0x3a, 0xc5, 0x24, 0xf4, 0x31, 0x09, 0x02, 0x16, + 0x2b, 0x4a, 0xe2, 0xd5, 0x5c, 0x82, 0x6b, 0xdb, 0x49, 0xb1, 0x27, 0x34, 0x60, 0x8d, 0xd7, 0x11, + 0x71, 0x68, 0x95, 0xee, 0x35, 0x29, 0x8f, 0x11, 0x82, 0xff, 0x6b, 0x84, 0xd7, 0xa6, 0xb4, 0x59, + 0x6d, 0xbe, 0x54, 0x15, 0x67, 0xd3, 0x85, 0xc9, 0x73, 0xd1, 0x3c, 0x64, 0x01, 0xa7, 0x68, 0x13, + 0xca, 0x6e, 0x72, 0xbb, 0x1b, 0x27, 0xd7, 0x02, 0x55, 0x5e, 0x99, 0xb7, 0x06, 0x39, 0x61, 0xa5, + 0xd2, 0x80, 0xdb, 0x3d, 0x9b, 0xe4, 0x5c, 0x15, 0xde, 0x21, 0xf5, 0x0c, 0xa0, 0xe7, 0x86, 0x2a, + 0x72, 0xcb, 0x92, 0xd6, 0x59, 0x89, 0x75, 0x96, 0xec, 0x83, 0xb2, 0xce, 0xda, 0x22, 0x5e, 0x47, + 0x50, 0x35, 0x85, 0x34, 0xbf, 0x68, 0x30, 0x75, 0xbe, 0x86, 0x92, 0xf2, 0x16, 0x2e, 0xa7, 0xa4, + 0xf0, 0x29, 0x6d, 0xf6, 0xbf, 0x3c, 0x5a, 0x36, 0xc6, 0x8f, 0x7f, 0xcc, 0x14, 0x8e, 0x7e, 0xce, + 0x14, 0x55, 0xde, 0x72, 0x4f, 0x1b, 0x47, 0xcf, 0x33, 0x0a, 0x46, 0x84, 0x82, 0xdb, 0x17, 0x2a, + 0x90, 0xcc, 0x32, 0x12, 0x26, 0x00, 0x09, 0x05, 0x5b, 0x24, 0x22, 0x8d, 0x8e, 0x41, 0xe6, 0x0e, + 0x5c, 0xc9, 0xdc, 0x2a, 0x49, 0x0f, 0xa1, 0x18, 0x8a, 0x1b, 0xe5, 0xd9, 0xdc, 0x60, 0x31, 0x0a, + 0xad, 0x30, 0xe6, 0x32, 0x5c, 0xed, 0x99, 0xf5, 0x82, 0xf0, 0x5a, 0xa7, 0x1d, 0x13, 0x30, 0xda, + 0x6b, 0x77, 0xa9, 0x2a, 0x3f, 0xb2, 0x33, 0x25, 0xc3, 0x15, 0x8d, 0x7e, 0x33, 0xb5, 0x03, 0xd7, + 0x45, 0xf4, 0x53, 0xee, 0x44, 0xec, 0xfd, 0x23, 0xd7, 0x8d, 0x28, 0xef, 0xf6, 0x7b, 0x12, 0x2e, + 0x85, 0x2c, 0x8a, 0x77, 0x7d, 0x57, 0x61, 0x8a, 0xc9, 0xe7, 0xa6, 0x8b, 0x6e, 0x00, 0x38, 0x35, + 0x12, 0x04, 0xb4, 0x9e, 0xbc, 0x8d, 0x88, 0xb7, 0x92, 0xba, 0xd9, 0x74, 0xcd, 0xc7, 0xa0, 0xf7, + 0x4b, 0xaa, 0x68, 0xdc, 0x84, 0x71, 0x2a, 0x1e, 0x76, 0x89, 0x7c, 0x51, 0xc9, 0xc7, 0x68, 0x3a, + 0x7c, 0xe5, 0x4f, 0x11, 0x46, 0x45, 0x16, 0xf4, 0x59, 0x03, 0xe8, 0x35, 0x18, 0xad, 0x0d, 0x76, + 0xaf, 0xff, 0x42, 0xe9, 0xf7, 0x72, 0xa2, 0x24, 0x59, 0xb3, 0xf2, 0xe1, 0xeb, 0xef, 0xc3, 0x91, + 0x45, 0x74, 0x07, 0xab, 0xad, 0xcf, 0x6e, 0x7b, 0x7a, 0x52, 0xf1, 0x41, 0xe2, 0x68, 0x1b, 0x7d, + 0xd2, 0xa0, 0x9c, 0x1a, 0x6c, 0x94, 0xaf, 0x72, 0xc7, 0x7c, 0xfd, 0x7e, 0x5e, 0x98, 0x62, 0xbc, + 0x20, 0x18, 0xcf, 0x21, 0xf3, 0x62, 0xc6, 0xe8, 0x50, 0x83, 0xa2, 0x9c, 0x36, 0x74, 0x77, 0x88, + 0x72, 0x99, 0x61, 0xd7, 0x2b, 0x39, 0x10, 0x8a, 0xdb, 0x9c, 0xe0, 0x66, 0xa0, 0xe9, 0xfe, 0xdc, + 0xe4, 0xc0, 0xa3, 0x23, 0x0d, 0x4a, 0xdd, 0xe9, 0x45, 0xab, 0xc3, 0xfa, 0x90, 0x5a, 0x0d, 0x7d, + 0x2d, 0x1f, 0x48, 0xd1, 0x5b, 0x11, 0xf4, 0x96, 0xd0, 0xc2, 0x20, 0xeb, 0x92, 0x26, 0x27, 0xcd, + 0x16, 0x16, 0xb6, 0xd1, 0x37, 0x0d, 0xc6, 0x32, 0x73, 0x8e, 0xd6, 0x87, 0xa8, 0xdd, 0x6f, 0xdd, + 0xf4, 0x07, 0xf9, 0x81, 0x8a, 0x78, 0x55, 0x10, 0x7f, 0x85, 0x5e, 0xf6, 0x27, 0xae, 0x36, 0x93, + 0xe3, 0x83, 0xde, 0xd6, 0xb6, 0x71, 0xb2, 0xcb, 0x1c, 0x1f, 0xa8, 0x0d, 0x6f, 0xe3, 0xec, 0x52, + 0x6e, 0x6c, 0x1f, 0x9f, 0x1a, 0xda, 0xc9, 0xa9, 0xa1, 0xfd, 0x3a, 0x35, 0xb4, 0x8f, 0x67, 0x46, + 0xe1, 0xe4, 0xcc, 0x28, 0x7c, 0x3f, 0x33, 0x0a, 0x6f, 0xd6, 0x3d, 0x3f, 0xae, 0x35, 0x6d, 0xcb, + 0x61, 0x0d, 0xac, 0xfe, 0x9b, 0xbe, 0xed, 0x2c, 0x7b, 0x0c, 0xb7, 0x56, 0x71, 0x83, 0xb9, 0xcd, + 0x3a, 0xe5, 0xff, 0x90, 0x88, 0xf7, 0x43, 0xca, 0xed, 0xa2, 0xf8, 0xeb, 0xad, 0xfe, 0x0d, 0x00, + 0x00, 0xff, 0xff, 0x32, 0xbb, 0x42, 0xe2, 0xcc, 0x07, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -481,6 +591,8 @@ type QueryClient interface { Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, 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) } type queryClient struct { @@ -527,6 +639,15 @@ func (c *queryClient) DenomHash(ctx context.Context, in *QueryDenomHashRequest, 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 +} + // QueryServer is the server API for Query service. type QueryServer interface { // DenomTrace queries a denomination trace information. @@ -537,6 +658,8 @@ type QueryServer interface { Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, 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) } // UnimplementedQueryServer can be embedded to have forward compatible implementations. @@ -555,6 +678,9 @@ func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsReq 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 RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) @@ -632,6 +758,24 @@ func _Query_DenomHash_Handler(srv interface{}, ctx context.Context, dec func(int 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) +} + var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "ibc.applications.transfer.v1.Query", HandlerType: (*QueryServer)(nil), @@ -652,6 +796,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "DenomHash", Handler: _Query_DenomHash_Handler, }, + { + MethodName: "EscrowAddress", + Handler: _Query_EscrowAddress_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "ibc/applications/transfer/v1/query.proto", @@ -924,6 +1072,73 @@ func (m *QueryDenomHashResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) 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 encodeVarintQuery(dAtA []byte, offset int, v uint64) int { offset -= sovQuery(v) base := offset @@ -1041,6 +1256,36 @@ func (m *QueryDenomHashResponse) Size() (n int) { 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 sovQuery(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -1721,6 +1966,202 @@ func (m *QueryDenomHashResponse) Unmarshal(dAtA []byte) error { } 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 skipQuery(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/modules/apps/transfer/types/query.pb.gw.go b/modules/apps/transfer/types/query.pb.gw.go index 6f17d4dc055..71474a1b2fc 100644 --- a/modules/apps/transfer/types/query.pb.gw.go +++ b/modules/apps/transfer/types/query.pb.gw.go @@ -193,6 +193,82 @@ func local_request_Query_DenomHash_0(ctx context.Context, marshaler runtime.Mars } +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 + +} + // 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. @@ -279,6 +355,26 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + 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.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) + 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()...) + + }) + return nil } @@ -400,6 +496,26 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + 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()...) + + }) + return nil } @@ -411,6 +527,8 @@ 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(true))) pattern_Query_DenomHash_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", "apps", "transfer", "v1", "denom_hashes", "trace"}, "", runtime.AssumeColonVerbOpt(true))) + + 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(true))) ) var ( @@ -421,4 +539,6 @@ var ( forward_Query_Params_0 = runtime.ForwardResponseMessage forward_Query_DenomHash_0 = runtime.ForwardResponseMessage + + forward_Query_EscrowAddress_0 = runtime.ForwardResponseMessage ) diff --git a/modules/apps/transfer/types/trace.go b/modules/apps/transfer/types/trace.go index 303ddd3769e..18caea1c535 100644 --- a/modules/apps/transfer/types/trace.go +++ b/modules/apps/transfer/types/trace.go @@ -12,6 +12,7 @@ import ( tmbytes "github.com/tendermint/tendermint/libs/bytes" tmtypes "github.com/tendermint/tendermint/types" + channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" host "github.com/cosmos/ibc-go/v3/modules/core/24-host" ) @@ -20,8 +21,11 @@ import ( // // Examples: // -// - "portidone/channelidone/uatom" => DenomTrace{Path: "portidone/channelidone", BaseDenom: "uatom"} -// - "uatom" => DenomTrace{Path: "", BaseDenom: "uatom"} +// - "portidone/channel-0/uatom" => DenomTrace{Path: "portidone/channel-0", BaseDenom: "uatom"} +// - "portidone/channel-0/portidtwo/channel-1/uatom" => DenomTrace{Path: "portidone/channel-0/portidtwo/channel-1", BaseDenom: "uatom"} +// - "portidone/channel-0/gamm/pool/1" => DenomTrace{Path: "portidone/channel-0", BaseDenom: "gamm/pool/1"} +// - "gamm/pool/1" => DenomTrace{Path: "", BaseDenom: "gamm/pool/1"} +// - "uatom" => DenomTrace{Path: "", BaseDenom: "uatom"} func ParseDenomTrace(rawDenom string) DenomTrace { denomSplit := strings.Split(rawDenom, "/") @@ -32,9 +36,10 @@ func ParseDenomTrace(rawDenom string) DenomTrace { } } + path, baseDenom := extractPathAndBaseFromFullDenom(denomSplit) return DenomTrace{ - Path: strings.Join(denomSplit[:len(denomSplit)-1], "/"), - BaseDenom: denomSplit[len(denomSplit)-1], + Path: path, + BaseDenom: baseDenom, } } @@ -70,6 +75,36 @@ func (dt DenomTrace) GetFullDenomPath() string { return dt.GetPrefix() + dt.BaseDenom } +// extractPathAndBaseFromFullDenom returns the trace path and the base denom from +// the elements that constitute the complete denom. +func extractPathAndBaseFromFullDenom(fullDenomItems []string) (string, string) { + var ( + path []string + baseDenom []string + ) + + length := len(fullDenomItems) + for i := 0; i < length; i = i + 2 { + // The IBC specification does not guarentee 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(fullDenomItems[i+1]) { + path = append(path, fullDenomItems[i], fullDenomItems[i+1]) + } else { + baseDenom = fullDenomItems[i:] + break + } + } + + return strings.Join(path, "/"), strings.Join(baseDenom, "/") +} + func validateTraceIdentifiers(identifiers []string) error { if len(identifiers) == 0 || len(identifiers)%2 != 0 { return fmt.Errorf("trace info must come in pairs of port and channel identifiers '{portID}/{channelID}', got the identifiers: %s", identifiers) @@ -143,8 +178,10 @@ func (t Traces) Sort() Traces { // ValidatePrefixedDenom checks that the denomination for an IBC fungible token packet denom is correctly prefixed. // The function will return no error if the given string follows one of the two formats: // -// - Prefixed denomination: '{portIDN}/{channelIDN}/.../{portID0}/{channelID0}/baseDenom' -// - Unprefixed denomination: 'baseDenom' +// - Prefixed denomination: '{portIDN}/{channelIDN}/.../{portID0}/{channelID0}/baseDenom' +// - Unprefixed denomination: 'baseDenom' +// +// 'baseDenom' may or may not contain '/'s func ValidatePrefixedDenom(denom string) error { denomSplit := strings.Split(denom, "/") if denomSplit[0] == denom && strings.TrimSpace(denom) != "" { @@ -156,14 +193,20 @@ func ValidatePrefixedDenom(denom string) error { return sdkerrors.Wrap(ErrInvalidDenomForTransfer, "base denomination cannot be blank") } - identifiers := denomSplit[:len(denomSplit)-1] + path, _ := extractPathAndBaseFromFullDenom(denomSplit) + if path == "" { + // NOTE: base denom contains slashes, so no base denomination validation + return nil + } + + identifiers := strings.Split(path, "/") return validateTraceIdentifiers(identifiers) } // 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 +// - 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 diff --git a/modules/apps/transfer/types/trace_test.go b/modules/apps/transfer/types/trace_test.go index e35fd33317b..ba0690423bd 100644 --- a/modules/apps/transfer/types/trace_test.go +++ b/modules/apps/transfer/types/trace_test.go @@ -14,10 +14,26 @@ func TestParseDenomTrace(t *testing.T) { }{ {"empty denom", "", DenomTrace{}}, {"base denom", "uatom", DenomTrace{BaseDenom: "uatom"}}, - {"trace info", "transfer/channelToA/uatom", DenomTrace{BaseDenom: "uatom", Path: "transfer/channelToA"}}, - {"incomplete path", "transfer/uatom", DenomTrace{BaseDenom: "uatom", Path: "transfer"}}, - {"invalid path (1)", "transfer//uatom", DenomTrace{BaseDenom: "uatom", Path: "transfer/"}}, - {"invalid path (2)", "transfer/channelToA/uatom/", DenomTrace{BaseDenom: "", Path: "transfer/channelToA/uatom"}}, + {"base denom ending with '/'", "uatom/", DenomTrace{BaseDenom: "uatom/"}}, + {"base denom with single '/'s", "gamm/pool/1", DenomTrace{BaseDenom: "gamm/pool/1"}}, + {"base denom with double '/'s", "gamm//pool//1", DenomTrace{BaseDenom: "gamm//pool//1"}}, + {"trace info", "transfer/channel-1/uatom", DenomTrace{BaseDenom: "uatom", Path: "transfer/channel-1"}}, + {"trace info with custom port", "customtransfer/channel-1/uatom", DenomTrace{BaseDenom: "uatom", Path: "customtransfer/channel-1"}}, + {"trace info with base denom ending in '/'", "transfer/channel-1/uatom/", DenomTrace{BaseDenom: "uatom/", Path: "transfer/channel-1"}}, + {"trace info with single '/' in base denom", "transfer/channel-1/erc20/0x85bcBCd7e79Ec36f4fBBDc54F90C643d921151AA", DenomTrace{BaseDenom: "erc20/0x85bcBCd7e79Ec36f4fBBDc54F90C643d921151AA", Path: "transfer/channel-1"}}, + {"trace info with multiple '/'s in base denom", "transfer/channel-1/gamm/pool/1", DenomTrace{BaseDenom: "gamm/pool/1", Path: "transfer/channel-1"}}, + {"trace info with multiple double '/'s in base denom", "transfer/channel-1/gamm//pool//1", DenomTrace{BaseDenom: "gamm//pool//1", Path: "transfer/channel-1"}}, + {"trace info with multiple port/channel pairs", "transfer/channel-1/transfer/channel-2/uatom", DenomTrace{BaseDenom: "uatom", Path: "transfer/channel-1/transfer/channel-2"}}, + {"trace info with multiple custom ports", "customtransfer/channel-1/alternativetransfer/channel-2/uatom", DenomTrace{BaseDenom: "uatom", Path: "customtransfer/channel-1/alternativetransfer/channel-2"}}, + {"incomplete path", "transfer/uatom", DenomTrace{BaseDenom: "transfer/uatom"}}, + {"invalid path (1)", "transfer//uatom", DenomTrace{BaseDenom: "transfer//uatom", Path: ""}}, + {"invalid path (2)", "channel-1/transfer/uatom", DenomTrace{BaseDenom: "channel-1/transfer/uatom"}}, + {"invalid path (3)", "uatom/transfer", DenomTrace{BaseDenom: "uatom/transfer"}}, + {"invalid path (4)", "transfer/channel-1", DenomTrace{BaseDenom: "transfer/channel-1"}}, + {"invalid path (5)", "transfer/channel-1/", DenomTrace{Path: "transfer/channel-1"}}, + {"invalid path (6)", "transfer/channel-1/transfer", DenomTrace{BaseDenom: "transfer", Path: "transfer/channel-1"}}, + {"invalid path (7)", "transfer/channel-1/transfer/channel-2", DenomTrace{Path: "transfer/channel-1/transfer/channel-2"}}, + {"invalid path (8)", "transfer/channelToA/uatom", DenomTrace{BaseDenom: "transfer/channelToA/uatom", Path: ""}}, } for _, tc := range testCases { @@ -33,7 +49,7 @@ func TestDenomTrace_IBCDenom(t *testing.T) { expDenom string }{ {"base denom", DenomTrace{BaseDenom: "uatom"}, "uatom"}, - {"trace info", DenomTrace{BaseDenom: "uatom", Path: "transfer/channelToA"}, "ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2"}, + {"trace info", DenomTrace{BaseDenom: "uatom", Path: "transfer/channel-1"}, "ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9"}, } for _, tc := range testCases { @@ -49,13 +65,15 @@ func TestDenomTrace_Validate(t *testing.T) { expError bool }{ {"base denom only", DenomTrace{BaseDenom: "uatom"}, false}, + {"base denom only with single '/'", DenomTrace{BaseDenom: "erc20/0x85bcBCd7e79Ec36f4fBBDc54F90C643d921151AA"}, false}, + {"base denom only with multiple '/'s", DenomTrace{BaseDenom: "gamm/pool/1"}, false}, {"empty DenomTrace", DenomTrace{}, true}, - {"valid single trace info", DenomTrace{BaseDenom: "uatom", Path: "transfer/channelToA"}, false}, - {"valid multiple trace info", DenomTrace{BaseDenom: "uatom", Path: "transfer/channelToA/transfer/channelToB"}, false}, + {"valid single trace info", DenomTrace{BaseDenom: "uatom", Path: "transfer/channel-1"}, false}, + {"valid multiple trace info", DenomTrace{BaseDenom: "uatom", Path: "transfer/channel-1/transfer/channel-2"}, false}, {"single trace identifier", DenomTrace{BaseDenom: "uatom", Path: "transfer"}, true}, - {"invalid port ID", DenomTrace{BaseDenom: "uatom", Path: "(transfer)/channelToA"}, true}, - {"invalid channel ID", DenomTrace{BaseDenom: "uatom", Path: "transfer/(channelToA)"}, true}, - {"empty base denom with trace", DenomTrace{BaseDenom: "", Path: "transfer/channelToA"}, true}, + {"invalid port ID", DenomTrace{BaseDenom: "uatom", Path: "(transfer)/channel-1"}, true}, + {"invalid channel ID", DenomTrace{BaseDenom: "uatom", Path: "transfer/(channel-1)"}, true}, + {"empty base denom with trace", DenomTrace{BaseDenom: "", Path: "transfer/channel-1"}, true}, } for _, tc := range testCases { @@ -75,16 +93,16 @@ func TestTraces_Validate(t *testing.T) { expError bool }{ {"empty Traces", Traces{}, false}, - {"valid multiple trace info", Traces{{BaseDenom: "uatom", Path: "transfer/channelToA/transfer/channelToB"}}, false}, + {"valid multiple trace info", Traces{{BaseDenom: "uatom", Path: "transfer/channel-1/transfer/channel-2"}}, false}, { "valid multiple trace info", Traces{ - {BaseDenom: "uatom", Path: "transfer/channelToA/transfer/channelToB"}, - {BaseDenom: "uatom", Path: "transfer/channelToA/transfer/channelToB"}, + {BaseDenom: "uatom", Path: "transfer/channel-1/transfer/channel-2"}, + {BaseDenom: "uatom", Path: "transfer/channel-1/transfer/channel-2"}, }, true, }, - {"empty base denom with trace", Traces{{BaseDenom: "", Path: "transfer/channelToA"}}, true}, + {"empty base denom with trace", Traces{{BaseDenom: "", Path: "transfer/channel-1"}}, true}, } for _, tc := range testCases { @@ -103,23 +121,25 @@ func TestValidatePrefixedDenom(t *testing.T) { denom string expError bool }{ - {"prefixed denom", "transfer/channelToA/uatom", false}, + {"prefixed denom", "transfer/channel-1/uatom", false}, + {"prefixed denom with '/'", "transfer/channel-1/gamm/pool/1", false}, + {"empty prefix", "/uatom", false}, + {"empty identifiers", "//uatom", false}, {"base denom", "uatom", false}, + {"base denom with single '/'", "erc20/0x85bcBCd7e79Ec36f4fBBDc54F90C643d921151AA", false}, + {"base denom with multiple '/'s", "gamm/pool/1", false}, + {"invalid port ID", "(transfer)/channel-1/uatom", true}, {"empty denom", "", true}, - {"empty prefix", "/uatom", true}, - {"empty identifiers", "//uatom", true}, {"single trace identifier", "transfer/", true}, - {"invalid port ID", "(transfer)/channelToA/uatom", true}, - {"invalid channel ID", "transfer/(channelToA)/uatom", true}, } for _, tc := range testCases { err := ValidatePrefixedDenom(tc.denom) if tc.expError { require.Error(t, err, tc.name) - continue + } else { + require.NoError(t, err, tc.name) } - require.NoError(t, err, tc.name) } } @@ -131,6 +151,7 @@ func TestValidateIBCDenom(t *testing.T) { }{ {"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}, diff --git a/modules/apps/transfer/types/tx.pb.go b/modules/apps/transfer/types/tx.pb.go index f240ba7368e..72a4f457b5c 100644 --- a/modules/apps/transfer/types/tx.pb.go +++ b/modules/apps/transfer/types/tx.pb.go @@ -50,6 +50,8 @@ type MsgTransfer struct { // Timeout timestamp in absolute nanoseconds since unix epoch. // The timeout is disabled when set to 0. TimeoutTimestamp uint64 `protobuf:"varint,7,opt,name=timeout_timestamp,json=timeoutTimestamp,proto3" json:"timeout_timestamp,omitempty" yaml:"timeout_timestamp"` + // optional memo + Memo string `protobuf:"bytes,8,opt,name=memo,proto3" json:"memo,omitempty"` } func (m *MsgTransfer) Reset() { *m = MsgTransfer{} } @@ -87,6 +89,8 @@ 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{} } @@ -122,6 +126,13 @@ func (m *MsgTransferResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgTransferResponse proto.InternalMessageInfo +func (m *MsgTransferResponse) GetSequence() uint64 { + if m != nil { + return m.Sequence + } + return 0 +} + func init() { proto.RegisterType((*MsgTransfer)(nil), "ibc.applications.transfer.v1.MsgTransfer") proto.RegisterType((*MsgTransferResponse)(nil), "ibc.applications.transfer.v1.MsgTransferResponse") @@ -132,38 +143,40 @@ func init() { } var fileDescriptor_7401ed9bed2f8e09 = []byte{ - // 494 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x93, 0x31, 0x6f, 0xd3, 0x40, - 0x14, 0xc7, 0x6d, 0x92, 0x86, 0x70, 0x51, 0x2b, 0x30, 0xb4, 0x72, 0xa3, 0x62, 0x47, 0x96, 0x90, - 0xc2, 0xc0, 0x9d, 0xdc, 0x0a, 0x55, 0xea, 0x84, 0xd2, 0x05, 0x86, 0x4a, 0x60, 0x75, 0x62, 0x29, - 0xf6, 0xf5, 0x70, 0x4e, 0xc4, 0xf7, 0xac, 0xbb, 0x8b, 0x45, 0xbf, 0x01, 0x23, 0x1f, 0xa1, 0x33, - 0x9f, 0xa4, 0x63, 0x47, 0xa6, 0x08, 0x25, 0x0b, 0x73, 0x3e, 0x01, 0x3a, 0xfb, 0x12, 0x92, 0x05, - 0x31, 0xd9, 0xef, 0xfd, 0x7f, 0xef, 0xfe, 0x7a, 0xf7, 0xde, 0xa1, 0x17, 0x3c, 0xa3, 0x24, 0x2d, - 0xcb, 0x09, 0xa7, 0xa9, 0xe6, 0x20, 0x14, 0xd1, 0x32, 0x15, 0xea, 0x33, 0x93, 0xa4, 0x8a, 0x89, - 0xfe, 0x8a, 0x4b, 0x09, 0x1a, 0xbc, 0x23, 0x9e, 0x51, 0xbc, 0x89, 0xe1, 0x15, 0x86, 0xab, 0xb8, - 0xff, 0x2c, 0x87, 0x1c, 0x6a, 0x90, 0x98, 0xbf, 0xa6, 0xa6, 0x1f, 0x50, 0x50, 0x05, 0x28, 0x92, - 0xa5, 0x8a, 0x91, 0x2a, 0xce, 0x98, 0x4e, 0x63, 0x42, 0x81, 0x0b, 0xab, 0x87, 0xc6, 0x9a, 0x82, - 0x64, 0x84, 0x4e, 0x38, 0x13, 0xda, 0x18, 0x36, 0x7f, 0x0d, 0x10, 0xfd, 0x68, 0xa1, 0xde, 0x85, - 0xca, 0x2f, 0xad, 0x93, 0x77, 0x8a, 0x7a, 0x0a, 0xa6, 0x92, 0xb2, 0xab, 0x12, 0xa4, 0xf6, 0xdd, - 0x81, 0x3b, 0x7c, 0x34, 0x3a, 0x58, 0xce, 0x42, 0xef, 0x26, 0x2d, 0x26, 0x67, 0xd1, 0x86, 0x18, - 0x25, 0xa8, 0x89, 0xde, 0x83, 0xd4, 0xde, 0x1b, 0xb4, 0x67, 0x35, 0x3a, 0x4e, 0x85, 0x60, 0x13, - 0xff, 0x41, 0x5d, 0x7b, 0xb8, 0x9c, 0x85, 0xfb, 0x5b, 0xb5, 0x56, 0x8f, 0x92, 0xdd, 0x26, 0x71, - 0xde, 0xc4, 0xde, 0x6b, 0xb4, 0xa3, 0xe1, 0x0b, 0x13, 0x7e, 0x6b, 0xe0, 0x0e, 0x7b, 0xc7, 0x87, - 0xb8, 0xe9, 0x0d, 0x9b, 0xde, 0xb0, 0xed, 0x0d, 0x9f, 0x03, 0x17, 0xa3, 0xf6, 0xdd, 0x2c, 0x74, - 0x92, 0x86, 0xf6, 0x0e, 0x50, 0x47, 0x31, 0x71, 0xcd, 0xa4, 0xdf, 0x36, 0x86, 0x89, 0x8d, 0xbc, - 0x3e, 0xea, 0x4a, 0x46, 0x19, 0xaf, 0x98, 0xf4, 0x77, 0x6a, 0x65, 0x1d, 0x7b, 0x9f, 0xd0, 0x9e, - 0xe6, 0x05, 0x83, 0xa9, 0xbe, 0x1a, 0x33, 0x9e, 0x8f, 0xb5, 0xdf, 0xa9, 0x3d, 0xfb, 0xd8, 0xcc, - 0xc0, 0xdc, 0x17, 0xb6, 0xb7, 0x54, 0xc5, 0xf8, 0x6d, 0x4d, 0x8c, 0x9e, 0x1b, 0xd3, 0xbf, 0xcd, - 0x6c, 0xd7, 0x47, 0xc9, 0xae, 0x4d, 0x34, 0xb4, 0xf7, 0x0e, 0x3d, 0x59, 0x11, 0xe6, 0xab, 0x74, - 0x5a, 0x94, 0xfe, 0xc3, 0x81, 0x3b, 0x6c, 0x8f, 0x8e, 0x96, 0xb3, 0xd0, 0xdf, 0x3e, 0x64, 0x8d, - 0x44, 0xc9, 0x63, 0x9b, 0xbb, 0x5c, 0xa5, 0xce, 0xba, 0xdf, 0x6e, 0x43, 0xe7, 0xf7, 0x6d, 0xe8, - 0x44, 0xfb, 0xe8, 0xe9, 0xc6, 0xac, 0x12, 0xa6, 0x4a, 0x10, 0x8a, 0x1d, 0x03, 0x6a, 0x5d, 0xa8, - 0xdc, 0x1b, 0xa3, 0xee, 0x7a, 0x8c, 0x2f, 0xf1, 0xbf, 0x96, 0x09, 0x6f, 0x9c, 0xd2, 0x8f, 0xff, - 0x1b, 0x5d, 0x19, 0x8e, 0x3e, 0xdc, 0xcd, 0x03, 0xf7, 0x7e, 0x1e, 0xb8, 0xbf, 0xe6, 0x81, 0xfb, - 0x7d, 0x11, 0x38, 0xf7, 0x8b, 0xc0, 0xf9, 0xb9, 0x08, 0x9c, 0x8f, 0xa7, 0x39, 0xd7, 0xe3, 0x69, - 0x86, 0x29, 0x14, 0xc4, 0xae, 0x26, 0xcf, 0xe8, 0xab, 0x1c, 0x48, 0x75, 0x42, 0x0a, 0xb8, 0x9e, - 0x4e, 0x98, 0x32, 0x4f, 0x61, 0xe3, 0x09, 0xe8, 0x9b, 0x92, 0xa9, 0xac, 0x53, 0xaf, 0xe3, 0xc9, - 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x0f, 0x07, 0x3c, 0x39, 0x2c, 0x03, 0x00, 0x00, + // 516 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x53, 0xc1, 0x6e, 0xd3, 0x40, + 0x10, 0xb5, 0x49, 0x1a, 0xc2, 0x46, 0xad, 0x60, 0x81, 0xca, 0x8d, 0x8a, 0x1d, 0x59, 0x42, 0x0a, + 0x07, 0x76, 0xe5, 0x56, 0xa8, 0x52, 0x4f, 0x28, 0xbd, 0xc0, 0xa1, 0x12, 0x58, 0x3d, 0x71, 0x29, + 0xf6, 0x76, 0x70, 0x56, 0xc4, 0x1e, 0xe3, 0xdd, 0x58, 0xf4, 0x0f, 0x38, 0xf2, 0x09, 0xfd, 0x12, + 0xce, 0x3d, 0xf6, 0xc8, 0x29, 0x42, 0xc9, 0x85, 0x73, 0xbe, 0x00, 0xad, 0xed, 0x84, 0xe4, 0x82, + 0x38, 0x79, 0x66, 0xde, 0x1b, 0xbf, 0x7d, 0x3b, 0xb3, 0xe4, 0xb9, 0x8c, 0x05, 0x8f, 0xf2, 0x7c, + 0x22, 0x45, 0xa4, 0x25, 0x66, 0x8a, 0xeb, 0x22, 0xca, 0xd4, 0x27, 0x28, 0x78, 0x19, 0x70, 0xfd, + 0x95, 0xe5, 0x05, 0x6a, 0xa4, 0x87, 0x32, 0x16, 0x6c, 0x93, 0xc6, 0x56, 0x34, 0x56, 0x06, 0xfd, + 0x27, 0x09, 0x26, 0x58, 0x11, 0xb9, 0x89, 0xea, 0x9e, 0xbe, 0x2b, 0x50, 0xa5, 0xa8, 0x78, 0x1c, + 0x29, 0xe0, 0x65, 0x10, 0x83, 0x8e, 0x02, 0x2e, 0x50, 0x66, 0x0d, 0xee, 0x19, 0x69, 0x81, 0x05, + 0x70, 0x31, 0x91, 0x90, 0x69, 0x23, 0x58, 0x47, 0x35, 0xc1, 0xff, 0xd1, 0x22, 0xbd, 0x73, 0x95, + 0x5c, 0x34, 0x4a, 0xf4, 0x84, 0xf4, 0x14, 0x4e, 0x0b, 0x01, 0x97, 0x39, 0x16, 0xda, 0xb1, 0x07, + 0xf6, 0xf0, 0xc1, 0x68, 0x7f, 0x39, 0xf3, 0xe8, 0x75, 0x94, 0x4e, 0x4e, 0xfd, 0x0d, 0xd0, 0x0f, + 0x49, 0x9d, 0xbd, 0xc3, 0x42, 0xd3, 0xd7, 0x64, 0xaf, 0xc1, 0xc4, 0x38, 0xca, 0x32, 0x98, 0x38, + 0xf7, 0xaa, 0xde, 0x83, 0xe5, 0xcc, 0x7b, 0xba, 0xd5, 0xdb, 0xe0, 0x7e, 0xb8, 0x5b, 0x17, 0xce, + 0xea, 0x9c, 0xbe, 0x22, 0x3b, 0x1a, 0x3f, 0x43, 0xe6, 0xb4, 0x06, 0xf6, 0xb0, 0x77, 0x74, 0xc0, + 0x6a, 0x6f, 0xcc, 0x78, 0x63, 0x8d, 0x37, 0x76, 0x86, 0x32, 0x1b, 0xb5, 0x6f, 0x67, 0x9e, 0x15, + 0xd6, 0x6c, 0xba, 0x4f, 0x3a, 0x0a, 0xb2, 0x2b, 0x28, 0x9c, 0xb6, 0x11, 0x0c, 0x9b, 0x8c, 0xf6, + 0x49, 0xb7, 0x00, 0x01, 0xb2, 0x84, 0xc2, 0xd9, 0xa9, 0x90, 0x75, 0x4e, 0x3f, 0x92, 0x3d, 0x2d, + 0x53, 0xc0, 0xa9, 0xbe, 0x1c, 0x83, 0x4c, 0xc6, 0xda, 0xe9, 0x54, 0x9a, 0x7d, 0x66, 0x66, 0x60, + 0xee, 0x8b, 0x35, 0xb7, 0x54, 0x06, 0xec, 0x4d, 0xc5, 0x18, 0x3d, 0x33, 0xa2, 0x7f, 0xcd, 0x6c, + 0xf7, 0xfb, 0xe1, 0x6e, 0x53, 0xa8, 0xd9, 0xf4, 0x2d, 0x79, 0xb4, 0x62, 0x98, 0xaf, 0xd2, 0x51, + 0x9a, 0x3b, 0xf7, 0x07, 0xf6, 0xb0, 0x3d, 0x3a, 0x5c, 0xce, 0x3c, 0x67, 0xfb, 0x27, 0x6b, 0x8a, + 0x1f, 0x3e, 0x6c, 0x6a, 0x17, 0xab, 0x12, 0xa5, 0xa4, 0x9d, 0x42, 0x8a, 0x4e, 0xb7, 0x32, 0x51, + 0xc5, 0xa7, 0xdd, 0x6f, 0x37, 0x9e, 0xf5, 0xfb, 0xc6, 0xb3, 0xfc, 0x80, 0x3c, 0xde, 0x98, 0x5f, + 0x08, 0x2a, 0xc7, 0x4c, 0x81, 0x71, 0xaf, 0xe0, 0xcb, 0x14, 0x32, 0x01, 0xd5, 0x10, 0xdb, 0xe1, + 0x3a, 0x3f, 0x42, 0xd2, 0x3a, 0x57, 0x09, 0x1d, 0x93, 0xee, 0x7a, 0xec, 0x2f, 0xd8, 0xbf, 0x96, + 0x8f, 0x6d, 0x28, 0xf4, 0x83, 0xff, 0xa6, 0xae, 0x0e, 0x33, 0x7a, 0x7f, 0x3b, 0x77, 0xed, 0xbb, + 0xb9, 0x6b, 0xff, 0x9a, 0xbb, 0xf6, 0xf7, 0x85, 0x6b, 0xdd, 0x2d, 0x5c, 0xeb, 0xe7, 0xc2, 0xb5, + 0x3e, 0x9c, 0x24, 0x52, 0x8f, 0xa7, 0x31, 0x13, 0x98, 0xf2, 0x66, 0x95, 0x65, 0x2c, 0x5e, 0x26, + 0xc8, 0xcb, 0x63, 0x9e, 0xe2, 0xd5, 0x74, 0x02, 0xca, 0x3c, 0x9d, 0x8d, 0x27, 0xa3, 0xaf, 0x73, + 0x50, 0x71, 0xa7, 0x5a, 0xdf, 0xe3, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x61, 0x6b, 0x91, 0x78, + 0x5c, 0x03, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -268,6 +281,13 @@ func (m *MsgTransfer) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + 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-- @@ -344,6 +364,11 @@ func (m *MsgTransferResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = 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 } @@ -387,6 +412,10 @@ func (m *MsgTransfer) Size() (n int) { if m.TimeoutTimestamp != 0 { n += 1 + sovTx(uint64(m.TimeoutTimestamp)) } + l = len(m.Memo) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } return n } @@ -396,6 +425,9 @@ func (m *MsgTransferResponse) Size() (n int) { } var l int _ = l + if m.Sequence != 0 { + n += 1 + sovTx(uint64(m.Sequence)) + } return n } @@ -647,6 +679,38 @@ func (m *MsgTransfer) Unmarshal(dAtA []byte) error { 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 default: iNdEx = preIndex skippy, err := skipTx(dAtA[iNdEx:]) @@ -697,6 +761,25 @@ func (m *MsgTransferResponse) Unmarshal(dAtA []byte) error { 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:]) diff --git a/modules/core/02-client/client/cli/cli.go b/modules/core/02-client/client/cli/cli.go index be33557d7ed..d06977016bd 100644 --- a/modules/core/02-client/client/cli/cli.go +++ b/modules/core/02-client/client/cli/cli.go @@ -22,6 +22,7 @@ func GetQueryCmd() *cobra.Command { GetCmdQueryClientState(), GetCmdQueryClientStatus(), GetCmdQueryConsensusStates(), + GetCmdQueryConsensusStateHeights(), GetCmdQueryConsensusState(), GetCmdQueryHeader(), GetCmdSelfConsensusState(), diff --git a/modules/core/02-client/client/cli/query.go b/modules/core/02-client/client/cli/query.go index d5e18679a28..56093a8368f 100644 --- a/modules/core/02-client/client/cli/query.go +++ b/modules/core/02-client/client/cli/query.go @@ -164,6 +164,49 @@ func GetCmdQueryConsensusStates() *cobra.Command { 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, host.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 { diff --git a/modules/core/02-client/keeper/client_test.go b/modules/core/02-client/keeper/client_test.go index e21f11130bb..aeee2b57e47 100644 --- a/modules/core/02-client/keeper/client_test.go +++ b/modules/core/02-client/keeper/client_test.go @@ -420,7 +420,6 @@ func (suite *KeeperTestSuite) TestUpgradeClient() { suite.Require().Error(err, "verify upgrade passed on invalid case: %s", tc.name) } } - } func (suite *KeeperTestSuite) TestCheckMisbehaviourAndUpdateState() { @@ -700,8 +699,6 @@ func (suite *KeeperTestSuite) TestUpdateClientEventEmission() { suite.Require().NoError(err) suite.Require().Equal(header, emittedHeader) } - } suite.Require().True(contains) - } diff --git a/modules/core/02-client/keeper/grpc_query.go b/modules/core/02-client/keeper/grpc_query.go index d4a2eca5a4f..e4e4fe05b15 100644 --- a/modules/core/02-client/keeper/grpc_query.go +++ b/modules/core/02-client/keeper/grpc_query.go @@ -83,7 +83,6 @@ func (q Keeper) ClientStates(c context.Context, req *types.QueryClientStatesRequ clientStates = append(clientStates, identifiedClient) return nil }) - if err != nil { return nil, err } @@ -177,7 +176,6 @@ func (q Keeper) ConsensusStates(c context.Context, req *types.QueryConsensusStat consensusStates = append(consensusStates, types.NewConsensusStateWithHeight(height, consensusState)) return true, nil }) - if err != nil { return nil, err } @@ -188,6 +186,45 @@ func (q Keeper) ConsensusStates(c context.Context, req *types.QueryConsensusStat }, nil } +// ConsensusStateHeights implements the Query/ConsensusStateHeights gRPC method +func (q Keeper) ConsensusStateHeights(c 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(c) + + var consensusStateHeights []types.Height + store := prefix.NewStore(ctx.KVStore(q.storeKey), host.FullClientKey(req.ClientId, []byte(fmt.Sprintf("%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 Keeper) ClientStatus(c context.Context, req *types.QueryClientStatusRequest) (*types.QueryClientStatusResponse, error) { if req == nil { diff --git a/modules/core/02-client/keeper/grpc_query_test.go b/modules/core/02-client/keeper/grpc_query_test.go index 5e393c33a97..e6d3a1ae1ba 100644 --- a/modules/core/02-client/keeper/grpc_query_test.go +++ b/modules/core/02-client/keeper/grpc_query_test.go @@ -2,7 +2,6 @@ package keeper_test import ( "fmt" - "time" codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -11,7 +10,6 @@ import ( "google.golang.org/grpc/metadata" "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" "github.com/cosmos/ibc-go/v3/modules/core/exported" ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" ibctesting "github.com/cosmos/ibc-go/v3/testing" @@ -28,19 +26,22 @@ func (suite *KeeperTestSuite) TestQueryClientState() { malleate func() expPass bool }{ - {"req is nil", + { + "req is nil", func() { req = nil }, false, }, - {"invalid clientID", + { + "invalid clientID", func() { req = &types.QueryClientStateRequest{} }, false, }, - {"client not found", + { + "client not found", func() { req = &types.QueryClientStateRequest{ ClientId: testClientID, @@ -100,7 +101,8 @@ func (suite *KeeperTestSuite) TestQueryClientStates() { malleate func() expPass bool }{ - {"req is nil", + { + "req is nil", func() { req = nil }, @@ -190,7 +192,8 @@ func (suite *KeeperTestSuite) TestQueryConsensusState() { malleate func() expPass bool }{ - {"req is nil", + { + "req is nil", func() { req = nil }, @@ -294,7 +297,7 @@ func (suite *KeeperTestSuite) TestQueryConsensusState() { func (suite *KeeperTestSuite) TestQueryConsensusStates() { var ( req *types.QueryConsensusStatesRequest - expConsensusStates = []types.ConsensusStateWithHeight{} + expConsensusStates []types.ConsensusStateWithHeight ) testCases := []struct { @@ -303,14 +306,7 @@ func (suite *KeeperTestSuite) TestQueryConsensusStates() { expPass bool }{ { - "invalid client identifier", - func() { - req = &types.QueryConsensusStatesRequest{} - }, - false, - }, - { - "empty pagination", + "success: without pagination", func() { req = &types.QueryConsensusStatesRequest{ ClientId: testClientID, @@ -334,29 +330,30 @@ func (suite *KeeperTestSuite) TestQueryConsensusStates() { { "success", func() { - cs := ibctmtypes.NewConsensusState( - suite.consensusState.Timestamp, commitmenttypes.NewMerkleRoot([]byte("hash1")), nil, - ) - cs2 := ibctmtypes.NewConsensusState( - suite.consensusState.Timestamp.Add(time.Second), commitmenttypes.NewMerkleRoot([]byte("hash2")), nil, - ) - - clientState := ibctmtypes.NewClientState( - testChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath, false, false, - ) - - // Use CreateClient to ensure that processedTime metadata gets stored. - clientId, err := suite.keeper.CreateClient(suite.ctx, clientState, cs) + path := ibctesting.NewPath(suite.chainA, suite.chainB) + suite.coordinator.SetupClients(path) + + height1 := path.EndpointA.GetClientState().GetLatestHeight().(types.Height) + expConsensusStates = append( + expConsensusStates, + types.NewConsensusStateWithHeight( + height1, + path.EndpointA.GetConsensusState(height1), + )) + + err := path.EndpointA.UpdateClient() suite.Require().NoError(err) - suite.keeper.SetClientConsensusState(suite.ctx, clientId, testClientHeight.Increment(), cs2) - // order is swapped because the res is sorted by client id - expConsensusStates = []types.ConsensusStateWithHeight{ - types.NewConsensusStateWithHeight(testClientHeight, cs), - types.NewConsensusStateWithHeight(testClientHeight.Increment().(types.Height), cs2), - } + height2 := path.EndpointA.GetClientState().GetLatestHeight().(types.Height) + expConsensusStates = append( + expConsensusStates, + types.NewConsensusStateWithHeight( + height2, + path.EndpointA.GetConsensusState(height2), + )) + req = &types.QueryConsensusStatesRequest{ - ClientId: clientId, + ClientId: path.EndpointA.ClientID, Pagination: &query.PageRequest{ Limit: 3, CountTotal: true, @@ -365,6 +362,13 @@ func (suite *KeeperTestSuite) TestQueryConsensusStates() { }, true, }, + { + "invalid client identifier", + func() { + req = &types.QueryConsensusStatesRequest{} + }, + false, + }, } for _, tc := range testCases { @@ -372,9 +376,8 @@ func (suite *KeeperTestSuite) TestQueryConsensusStates() { suite.SetupTest() // reset tc.malleate() - ctx := sdk.WrapSDKContext(suite.ctx) - - res, err := suite.queryClient.ConsensusStates(ctx, req) + ctx := sdk.WrapSDKContext(suite.chainA.GetContext()) + res, err := suite.chainA.QueryServer.ConsensusStates(ctx, req) if tc.expPass { suite.Require().NoError(err) @@ -395,30 +398,119 @@ func (suite *KeeperTestSuite) TestQueryConsensusStates() { } } -func (suite *KeeperTestSuite) TestQueryClientStatus() { +func (suite *KeeperTestSuite) TestQueryConsensusStateHeights() { var ( - req *types.QueryClientStatusRequest + req *types.QueryConsensusStateHeightsRequest + expConsensusStateHeights []types.Height ) + testCases := []struct { + msg string + malleate func() + expPass bool + }{ + { + "success: without pagination", + func() { + req = &types.QueryConsensusStateHeightsRequest{ + ClientId: testClientID, + } + }, + true, + }, + { + "success: response contains no results", + func() { + req = &types.QueryConsensusStateHeightsRequest{ + ClientId: testClientID, + Pagination: &query.PageRequest{ + Limit: 3, + CountTotal: true, + }, + } + }, + true, + }, + { + "success: returns consensus heights", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + suite.coordinator.SetupClients(path) + + expConsensusStateHeights = append(expConsensusStateHeights, path.EndpointA.GetClientState().GetLatestHeight().(types.Height)) + + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + expConsensusStateHeights = append(expConsensusStateHeights, path.EndpointA.GetClientState().GetLatestHeight().(types.Height)) + + req = &types.QueryConsensusStateHeightsRequest{ + ClientId: path.EndpointA.ClientID, + Pagination: &query.PageRequest{ + Limit: 3, + CountTotal: true, + }, + } + }, + true, + }, + { + "invalid client identifier", + func() { + req = &types.QueryConsensusStateHeightsRequest{} + }, + false, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := sdk.WrapSDKContext(suite.chainA.GetContext()) + res, err := suite.chainA.QueryServer.ConsensusStateHeights(ctx, req) + + if tc.expPass { + 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) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryClientStatus() { + var req *types.QueryClientStatusRequest + testCases := []struct { msg string malleate func() expPass bool expStatus string }{ - {"req is nil", + { + "req is nil", func() { req = nil }, false, "", }, - {"invalid clientID", + { + "invalid clientID", func() { req = &types.QueryClientStatusRequest{} }, false, "", }, - {"client not found", + { + "client not found", func() { req = &types.QueryClientStatusRequest{ ClientId: ibctesting.InvalidID, @@ -503,7 +595,8 @@ func (suite *KeeperTestSuite) TestQueryUpgradedConsensusStates() { malleate func() expPass bool }{ - {"req is nil", + { + "req is nil", func() { req = nil }, diff --git a/modules/core/02-client/keeper/proposal_test.go b/modules/core/02-client/keeper/proposal_test.go index bec5aa0a78a..277354f9e9b 100644 --- a/modules/core/02-client/keeper/proposal_test.go +++ b/modules/core/02-client/keeper/proposal_test.go @@ -154,7 +154,6 @@ func (suite *KeeperTestSuite) TestClientUpdateProposal() { } }) } - } func (suite *KeeperTestSuite) TestHandleUpgradeProposal() { @@ -206,7 +205,7 @@ func (suite *KeeperTestSuite) TestHandleUpgradeProposal() { suite.Run(tc.name, func() { suite.SetupTest() // reset - oldPlan.Height = 0 //reset + oldPlan.Height = 0 // reset path := ibctesting.NewPath(suite.chainA, suite.chainB) suite.coordinator.SetupClients(path) @@ -276,5 +275,4 @@ func (suite *KeeperTestSuite) TestHandleUpgradeProposal() { } }) } - } diff --git a/modules/core/02-client/legacy/v100/genesis.go b/modules/core/02-client/legacy/v100/genesis.go index fa052ade3f8..014dd3870c0 100644 --- a/modules/core/02-client/legacy/v100/genesis.go +++ b/modules/core/02-client/legacy/v100/genesis.go @@ -123,13 +123,11 @@ func MigrateGenesis(cdc codec.BinaryCodec, clientGenState *types.GenesisState, g Key: ibctmtypes.IterationKey(height), Value: host.ConsensusStateKey(height), }) - } } } } - } // if we have metadata for unexipred consensus states, add it to consensusMetadata diff --git a/modules/core/02-client/legacy/v100/genesis_test.go b/modules/core/02-client/legacy/v100/genesis_test.go index 4835ce98213..c4daa6c9bfc 100644 --- a/modules/core/02-client/legacy/v100/genesis_test.go +++ b/modules/core/02-client/legacy/v100/genesis_test.go @@ -254,7 +254,6 @@ func (suite *LegacyTestSuite) TestMigrateGenesisTendermint() { suite.Require().NotEqual(height, consensusState.Height) } } - } for _, client := range migrated.ClientsMetadata { if client.ClientId == path1.EndpointA.ClientID { @@ -275,7 +274,6 @@ func (suite *LegacyTestSuite) TestMigrateGenesisTendermint() { suite.Require().NotEqual(height, consensusState.Height) } } - } for _, client := range migrated.ClientsMetadata { if client.ClientId == path2.EndpointA.ClientID { @@ -285,7 +283,6 @@ func (suite *LegacyTestSuite) TestMigrateGenesisTendermint() { suite.Require().NotEqual(ibctmtypes.IterationKey(height), metadata.Key) } } - } } bz, err := clientCtx.JSONCodec.MarshalJSON(&expectedClientGenState) diff --git a/modules/core/02-client/proposal_handler_test.go b/modules/core/02-client/proposal_handler_test.go index 4ef2798d633..b83455b39c2 100644 --- a/modules/core/02-client/proposal_handler_test.go +++ b/modules/core/02-client/proposal_handler_test.go @@ -84,5 +84,4 @@ func (suite *ClientTestSuite) TestNewClientUpdateProposalHandler() { } }) } - } diff --git a/modules/core/02-client/types/client_test.go b/modules/core/02-client/types/client_test.go index 9f41843c064..cefe567619c 100644 --- a/modules/core/02-client/types/client_test.go +++ b/modules/core/02-client/types/client_test.go @@ -10,9 +10,7 @@ import ( ) func (suite *TypesTestSuite) TestMarshalConsensusStateWithHeight() { - var ( - cswh types.ConsensusStateWithHeight - ) + var cswh types.ConsensusStateWithHeight testCases := []struct { name string diff --git a/modules/core/02-client/types/codec_test.go b/modules/core/02-client/types/codec_test.go index 2e4364a37a7..e29dc32a044 100644 --- a/modules/core/02-client/types/codec_test.go +++ b/modules/core/02-client/types/codec_test.go @@ -18,7 +18,6 @@ type caseAny struct { } func (suite *TypesTestSuite) TestPackClientState() { - testCases := []struct { name string clientState exported.ClientState diff --git a/modules/core/02-client/types/encoding_test.go b/modules/core/02-client/types/encoding_test.go index 9bd619260ea..75ee99e6c85 100644 --- a/modules/core/02-client/types/encoding_test.go +++ b/modules/core/02-client/types/encoding_test.go @@ -6,7 +6,6 @@ import ( ) func (suite *TypesTestSuite) TestMarshalHeader() { - cdc := suite.chainA.App.AppCodec() h := &ibctmtypes.Header{ TrustedHeight: types.NewHeight(4, 100), @@ -26,5 +25,4 @@ func (suite *TypesTestSuite) TestMarshalHeader() { invalidHeader, err := types.UnmarshalHeader(cdc, []byte("invalid bytes")) suite.Require().Error(err) suite.Require().Nil(invalidHeader) - } diff --git a/modules/core/02-client/types/genesis.go b/modules/core/02-client/types/genesis.go index a272404054f..ccf797e0d49 100644 --- a/modules/core/02-client/types/genesis.go +++ b/modules/core/02-client/types/genesis.go @@ -196,7 +196,6 @@ func (gs GenesisState) Validate() error { if err := gm.Validate(); err != nil { return fmt.Errorf("invalid client metadata %v clientID %s index %d: %w", gm, clientMetadata.ClientId, i, err) } - } } diff --git a/modules/core/02-client/types/height_test.go b/modules/core/02-client/types/height_test.go index c31bbaabf21..16415c3d61e 100644 --- a/modules/core/02-client/types/height_test.go +++ b/modules/core/02-client/types/height_test.go @@ -126,7 +126,6 @@ func TestParseChainID(t *testing.T) { revision := types.ParseChainID(tc.chainID) require.Equal(t, tc.revision, revision, "chainID %s returns incorrect revision", tc.chainID) } - } func TestSetRevisionNumber(t *testing.T) { diff --git a/modules/core/02-client/types/msgs.go b/modules/core/02-client/types/msgs.go index d80fe8b0592..f3d49765c25 100644 --- a/modules/core/02-client/types/msgs.go +++ b/modules/core/02-client/types/msgs.go @@ -30,11 +30,11 @@ var ( ) // NewMsgCreateClient creates a new MsgCreateClient instance +// //nolint:interfacer func NewMsgCreateClient( clientState exported.ClientState, consensusState exported.ConsensusState, signer string, ) (*MsgCreateClient, error) { - anyClientState, err := PackClientState(clientState) if err != nil { return nil, err @@ -103,6 +103,7 @@ func (msg MsgCreateClient) UnpackInterfaces(unpacker codectypes.AnyUnpacker) err } // NewMsgUpdateClient creates a new MsgUpdateClient instance +// //nolint:interfacer func NewMsgUpdateClient(id string, header exported.Header, signer string) (*MsgUpdateClient, error) { anyHeader, err := PackHeader(header) @@ -154,7 +155,8 @@ func (msg MsgUpdateClient) UnpackInterfaces(unpacker codectypes.AnyUnpacker) err // NewMsgUpgradeClient creates a new MsgUpgradeClient instance // nolint: interfacer func NewMsgUpgradeClient(clientID string, clientState exported.ClientState, consState exported.ConsensusState, - proofUpgradeClient, proofUpgradeConsState []byte, signer string) (*MsgUpgradeClient, error) { + proofUpgradeClient, proofUpgradeConsState []byte, signer string, +) (*MsgUpgradeClient, error) { anyClient, err := PackClientState(clientState) if err != nil { return nil, err @@ -228,6 +230,7 @@ func (msg MsgUpgradeClient) UnpackInterfaces(unpacker codectypes.AnyUnpacker) er } // NewMsgSubmitMisbehaviour creates a new MsgSubmitMisbehaviour instance. +// //nolint:interfacer func NewMsgSubmitMisbehaviour(clientID string, misbehaviour exported.Misbehaviour, signer string) (*MsgSubmitMisbehaviour, error) { anyMisbehaviour, err := PackMisbehaviour(misbehaviour) diff --git a/modules/core/02-client/types/msgs_test.go b/modules/core/02-client/types/msgs_test.go index 466a14cfb8d..d7c9e0d1ecd 100644 --- a/modules/core/02-client/types/msgs_test.go +++ b/modules/core/02-client/types/msgs_test.go @@ -215,7 +215,6 @@ func (suite *TypesTestSuite) TestMarshalMsgUpdateClient() { chainATendermint := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint) msg, err = types.NewMsgUpdateClient("tendermint", chainATendermint.CurrentTMClientHeader(), suite.chainA.SenderAccount.GetAddress().String()) suite.Require().NoError(err) - }, }, } @@ -504,7 +503,6 @@ func (suite *TypesTestSuite) TestMarshalMsgSubmitMisbehaviour() { misbehaviour := ibctmtypes.NewMisbehaviour("tendermint", header1, header2) msg, err = types.NewMsgSubmitMisbehaviour("tendermint", misbehaviour, suite.chainA.SenderAccount.GetAddress().String()) suite.Require().NoError(err) - }, }, } diff --git a/modules/core/02-client/types/proposal_test.go b/modules/core/02-client/types/proposal_test.go index a32dcdac4e8..d1a6f52cf71 100644 --- a/modules/core/02-client/types/proposal_test.go +++ b/modules/core/02-client/types/proposal_test.go @@ -117,7 +117,6 @@ func (suite *TypesTestSuite) TestUpgradeProposalValidateBasic() { "fails validate abstract - empty title", func() { proposal, err = types.NewUpgradeProposal("", ibctesting.Description, plan, cs) suite.Require().NoError(err) - }, false, }, { @@ -199,7 +198,6 @@ func (suite *TypesTestSuite) TestMarshalUpgradeProposal() { // unpack client state _, err = types.UnpackClientState(newUp.UpgradedClientState) suite.Require().NoError(err) - } func (suite *TypesTestSuite) TestUpgradeString() { diff --git a/modules/core/02-client/types/query.pb.go b/modules/core/02-client/types/query.pb.go index 7ea1d377be6..bcb5ccd498e 100644 --- a/modules/core/02-client/types/query.pb.go +++ b/modules/core/02-client/types/query.pb.go @@ -500,6 +500,118 @@ func (m *QueryConsensusStatesResponse) GetPagination() *query.PageResponse { 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 { @@ -511,7 +623,7 @@ func (m *QueryClientStatusRequest) Reset() { *m = QueryClientStatusReque func (m *QueryClientStatusRequest) String() string { return proto.CompactTextString(m) } func (*QueryClientStatusRequest) ProtoMessage() {} func (*QueryClientStatusRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_dc42cdfd1d52d76e, []int{8} + return fileDescriptor_dc42cdfd1d52d76e, []int{10} } func (m *QueryClientStatusRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -557,7 +669,7 @@ func (m *QueryClientStatusResponse) Reset() { *m = QueryClientStatusResp func (m *QueryClientStatusResponse) String() string { return proto.CompactTextString(m) } func (*QueryClientStatusResponse) ProtoMessage() {} func (*QueryClientStatusResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_dc42cdfd1d52d76e, []int{9} + return fileDescriptor_dc42cdfd1d52d76e, []int{11} } func (m *QueryClientStatusResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -602,7 +714,7 @@ func (m *QueryClientParamsRequest) Reset() { *m = QueryClientParamsReque func (m *QueryClientParamsRequest) String() string { return proto.CompactTextString(m) } func (*QueryClientParamsRequest) ProtoMessage() {} func (*QueryClientParamsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_dc42cdfd1d52d76e, []int{10} + return fileDescriptor_dc42cdfd1d52d76e, []int{12} } func (m *QueryClientParamsRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -642,7 +754,7 @@ func (m *QueryClientParamsResponse) Reset() { *m = QueryClientParamsResp func (m *QueryClientParamsResponse) String() string { return proto.CompactTextString(m) } func (*QueryClientParamsResponse) ProtoMessage() {} func (*QueryClientParamsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_dc42cdfd1d52d76e, []int{11} + return fileDescriptor_dc42cdfd1d52d76e, []int{13} } func (m *QueryClientParamsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -687,7 +799,7 @@ func (m *QueryUpgradedClientStateRequest) Reset() { *m = QueryUpgradedCl func (m *QueryUpgradedClientStateRequest) String() string { return proto.CompactTextString(m) } func (*QueryUpgradedClientStateRequest) ProtoMessage() {} func (*QueryUpgradedClientStateRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_dc42cdfd1d52d76e, []int{12} + return fileDescriptor_dc42cdfd1d52d76e, []int{14} } func (m *QueryUpgradedClientStateRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -727,7 +839,7 @@ func (m *QueryUpgradedClientStateResponse) Reset() { *m = QueryUpgradedC func (m *QueryUpgradedClientStateResponse) String() string { return proto.CompactTextString(m) } func (*QueryUpgradedClientStateResponse) ProtoMessage() {} func (*QueryUpgradedClientStateResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_dc42cdfd1d52d76e, []int{13} + return fileDescriptor_dc42cdfd1d52d76e, []int{15} } func (m *QueryUpgradedClientStateResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -772,7 +884,7 @@ func (m *QueryUpgradedConsensusStateRequest) Reset() { *m = QueryUpgrade func (m *QueryUpgradedConsensusStateRequest) String() string { return proto.CompactTextString(m) } func (*QueryUpgradedConsensusStateRequest) ProtoMessage() {} func (*QueryUpgradedConsensusStateRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_dc42cdfd1d52d76e, []int{14} + return fileDescriptor_dc42cdfd1d52d76e, []int{16} } func (m *QueryUpgradedConsensusStateRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -812,7 +924,7 @@ func (m *QueryUpgradedConsensusStateResponse) Reset() { *m = QueryUpgrad func (m *QueryUpgradedConsensusStateResponse) String() string { return proto.CompactTextString(m) } func (*QueryUpgradedConsensusStateResponse) ProtoMessage() {} func (*QueryUpgradedConsensusStateResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_dc42cdfd1d52d76e, []int{15} + return fileDescriptor_dc42cdfd1d52d76e, []int{17} } func (m *QueryUpgradedConsensusStateResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -857,6 +969,8 @@ func init() { 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") @@ -870,69 +984,73 @@ func init() { func init() { proto.RegisterFile("ibc/core/client/v1/query.proto", fileDescriptor_dc42cdfd1d52d76e) } var fileDescriptor_dc42cdfd1d52d76e = []byte{ - // 983 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x57, 0xcd, 0x6f, 0x1b, 0x45, - 0x14, 0xcf, 0xa4, 0x69, 0xd4, 0x3e, 0xbb, 0x09, 0x9a, 0xa6, 0xa9, 0xbb, 0x2d, 0x8e, 0xbb, 0x41, - 0x34, 0x2d, 0xc9, 0x4e, 0xe2, 0x40, 0xd3, 0x0b, 0x07, 0x52, 0xa9, 0xb4, 0x97, 0x52, 0x16, 0x21, - 0x24, 0x24, 0x14, 0xed, 0xae, 0x27, 0x9b, 0x95, 0xec, 0x1d, 0xd7, 0xb3, 0x6b, 0x29, 0xaa, 0x72, - 0xa0, 0x47, 0x4e, 0x48, 0x48, 0x5c, 0x91, 0x38, 0x72, 0xa8, 0x38, 0x20, 0x71, 0xe5, 0x84, 0x7a, - 0xac, 0x04, 0x07, 0x4e, 0x04, 0x25, 0xfc, 0x21, 0x68, 0x67, 0x66, 0xe3, 0x1d, 0x7b, 0x8c, 0xd7, - 0x88, 0xde, 0x76, 0xde, 0xe7, 0xef, 0x7d, 0xcc, 0x6f, 0xb4, 0x50, 0x8f, 0xfc, 0x80, 0x04, 0xac, - 0x47, 0x49, 0xd0, 0x8e, 0x68, 0x9c, 0x90, 0xfe, 0x16, 0x79, 0x9a, 0xd2, 0xde, 0xa1, 0xd3, 0xed, - 0xb1, 0x84, 0x61, 0x1c, 0xf9, 0x81, 0x93, 0xe9, 0x1d, 0xa9, 0x77, 0xfa, 0x5b, 0xd6, 0x9d, 0x80, - 0xf1, 0x0e, 0xe3, 0xc4, 0xf7, 0x38, 0x95, 0xc6, 0xa4, 0xbf, 0xe5, 0xd3, 0xc4, 0xdb, 0x22, 0x5d, - 0x2f, 0x8c, 0x62, 0x2f, 0x89, 0x58, 0x2c, 0xfd, 0xad, 0x15, 0x43, 0x7c, 0x15, 0x49, 0x1a, 0x5c, - 0x0b, 0x19, 0x0b, 0xdb, 0x94, 0x88, 0x93, 0x9f, 0xee, 0x13, 0x2f, 0x56, 0xb9, 0xad, 0x1b, 0x4a, - 0xe5, 0x75, 0x23, 0xe2, 0xc5, 0x31, 0x4b, 0x44, 0x60, 0xae, 0xb4, 0x4b, 0x21, 0x0b, 0x99, 0xf8, - 0x24, 0xd9, 0x97, 0x94, 0xda, 0x77, 0xe1, 0xea, 0xc7, 0x19, 0xa2, 0xfb, 0x22, 0xc7, 0x27, 0x89, - 0x97, 0x50, 0x97, 0x3e, 0x4d, 0x29, 0x4f, 0xf0, 0x75, 0xb8, 0x28, 0x33, 0xef, 0x45, 0xad, 0x1a, - 0x6a, 0xa0, 0xb5, 0x8b, 0xee, 0x05, 0x29, 0x78, 0xd4, 0xb2, 0x5f, 0x20, 0xa8, 0x8d, 0x3a, 0xf2, - 0x2e, 0x8b, 0x39, 0xc5, 0x3b, 0x50, 0x55, 0x9e, 0x3c, 0x93, 0x0b, 0xe7, 0x4a, 0x73, 0xc9, 0x91, - 0xf8, 0x9c, 0x1c, 0xba, 0xf3, 0x41, 0x7c, 0xe8, 0x56, 0x82, 0x41, 0x00, 0xbc, 0x04, 0xe7, 0xbb, - 0x3d, 0xc6, 0xf6, 0x6b, 0xb3, 0x0d, 0xb4, 0x56, 0x75, 0xe5, 0x01, 0xdf, 0x87, 0xaa, 0xf8, 0xd8, - 0x3b, 0xa0, 0x51, 0x78, 0x90, 0xd4, 0xce, 0x89, 0x70, 0x96, 0x33, 0xda, 0x6a, 0xe7, 0xa1, 0xb0, - 0xd8, 0x9d, 0x7b, 0xf9, 0xe7, 0xca, 0x8c, 0x5b, 0x11, 0x5e, 0x52, 0x64, 0xfb, 0xa3, 0x78, 0x79, - 0x5e, 0xe9, 0x03, 0x80, 0xc1, 0x20, 0x14, 0xda, 0xb7, 0x1d, 0x39, 0x35, 0x27, 0x9b, 0x9a, 0x23, - 0x47, 0xac, 0xa6, 0xe6, 0x3c, 0xf1, 0xc2, 0xbc, 0x4b, 0x6e, 0xc1, 0xd3, 0xfe, 0x1d, 0xc1, 0x35, - 0x43, 0x12, 0xd5, 0x95, 0x18, 0x2e, 0x15, 0xbb, 0xc2, 0x6b, 0xa8, 0x71, 0x6e, 0xad, 0xd2, 0xbc, - 0x6d, 0xaa, 0xe3, 0x51, 0x8b, 0xc6, 0x49, 0xb4, 0x1f, 0xd1, 0x56, 0x21, 0xd4, 0x6e, 0x3d, 0x2b, - 0xeb, 0x87, 0xe3, 0x95, 0x65, 0xa3, 0x9a, 0xbb, 0xd5, 0x42, 0x2f, 0x39, 0xfe, 0x50, 0xab, 0x6a, - 0x56, 0x54, 0x75, 0x6b, 0x62, 0x55, 0x12, 0xac, 0x56, 0xd6, 0x8f, 0x08, 0x2c, 0x59, 0x56, 0xa6, - 0x8a, 0x79, 0xca, 0x4b, 0xef, 0x09, 0xbe, 0x05, 0x8b, 0x3d, 0xda, 0x8f, 0x78, 0xc4, 0xe2, 0xbd, - 0x38, 0xed, 0xf8, 0xb4, 0x27, 0x90, 0xcc, 0xb9, 0x0b, 0xb9, 0xf8, 0xb1, 0x90, 0x6a, 0x86, 0x85, - 0x39, 0x17, 0x0c, 0xe5, 0x20, 0xf1, 0x2a, 0x5c, 0x6a, 0x67, 0xf5, 0x25, 0xb9, 0xd9, 0x5c, 0x03, - 0xad, 0x5d, 0x70, 0xab, 0x52, 0xa8, 0xa6, 0xfd, 0x33, 0x82, 0xeb, 0x46, 0xc8, 0x6a, 0x16, 0xef, - 0xc3, 0x62, 0x90, 0x6b, 0x4a, 0x2c, 0xe9, 0x42, 0xa0, 0x85, 0x79, 0x9d, 0x7b, 0xfa, 0xdc, 0x8c, - 0x9c, 0x97, 0xea, 0xf6, 0x03, 0xc3, 0xc8, 0xff, 0xcb, 0x22, 0xff, 0x8a, 0xe0, 0x86, 0x19, 0x84, - 0xea, 0xdf, 0x17, 0xf0, 0xc6, 0x50, 0xff, 0xf2, 0x75, 0x5e, 0x37, 0x95, 0xab, 0x87, 0xf9, 0x2c, - 0x4a, 0x0e, 0xb4, 0x06, 0x2c, 0xea, 0xed, 0xfd, 0x1f, 0x57, 0x77, 0x67, 0xe4, 0xd6, 0xa7, 0xa5, - 0x3a, 0x69, 0x6f, 0x8f, 0xdc, 0xe4, 0x74, 0x50, 0xfd, 0x32, 0xcc, 0x73, 0x21, 0x51, 0x6e, 0xea, - 0x64, 0x5b, 0x5a, 0xb6, 0x27, 0x5e, 0xcf, 0xeb, 0xe4, 0xd9, 0xec, 0x8f, 0xb4, 0x80, 0xb9, 0x4e, - 0x05, 0x6c, 0xc2, 0x7c, 0x57, 0x48, 0xd4, 0x16, 0x1a, 0x77, 0x46, 0xf9, 0x28, 0x4b, 0xfb, 0x26, - 0xac, 0x88, 0x80, 0x9f, 0x76, 0xc3, 0x9e, 0xd7, 0xd2, 0x98, 0x20, 0xcf, 0xd9, 0x86, 0xc6, 0x78, - 0x13, 0x95, 0xfa, 0x21, 0x5c, 0x49, 0x95, 0x7a, 0xaf, 0x34, 0x69, 0x5f, 0x4e, 0x47, 0x23, 0xda, - 0x6f, 0x81, 0xad, 0x67, 0x33, 0xb1, 0x85, 0x9d, 0xc2, 0xea, 0xbf, 0x5a, 0x29, 0x58, 0x8f, 0xa1, - 0x36, 0x80, 0x35, 0xc5, 0x4d, 0x5d, 0x4e, 0x8d, 0x71, 0x9b, 0x5f, 0x56, 0xe0, 0xbc, 0xc8, 0x8b, - 0xbf, 0x43, 0x50, 0x29, 0xc0, 0xc6, 0xef, 0x98, 0x7a, 0x3d, 0xe6, 0x4d, 0xb4, 0xd6, 0xcb, 0x19, - 0xcb, 0x22, 0xec, 0xf7, 0x9e, 0xff, 0xf6, 0xf7, 0x37, 0xb3, 0x04, 0x6f, 0x90, 0xb1, 0xaf, 0xba, - 0xba, 0x3c, 0xe4, 0xd9, 0xd9, 0x2a, 0x1e, 0xe1, 0x6f, 0x11, 0x54, 0x8b, 0xbc, 0x8e, 0x4b, 0x65, - 0xcd, 0x37, 0xcd, 0xda, 0x28, 0x69, 0xad, 0x40, 0xde, 0x16, 0x20, 0x57, 0xf1, 0xcd, 0x89, 0x20, - 0xf1, 0x31, 0x82, 0x05, 0xbd, 0xaf, 0xd8, 0x19, 0x9f, 0xcc, 0x34, 0x7e, 0x8b, 0x94, 0xb6, 0x57, - 0xf0, 0xda, 0x02, 0xde, 0x3e, 0x6e, 0x19, 0xe1, 0x0d, 0x71, 0x50, 0xb1, 0x8d, 0x24, 0x7f, 0x37, - 0xc8, 0xb3, 0xa1, 0x17, 0xe8, 0x88, 0x48, 0x82, 0x2e, 0x28, 0xa4, 0xe0, 0x08, 0xbf, 0x40, 0xb0, - 0x38, 0xc4, 0x79, 0xb8, 0x2c, 0xe4, 0xb3, 0x01, 0x6c, 0x96, 0x77, 0x50, 0x45, 0xde, 0x13, 0x45, - 0x36, 0xf1, 0xe6, 0xb4, 0x45, 0xe2, 0xef, 0xb5, 0x5d, 0x49, 0xcb, 0xed, 0x4a, 0x3a, 0xd5, 0xae, - 0x0c, 0x88, 0xaf, 0xf4, 0x42, 0xa7, 0x3a, 0xc8, 0xaf, 0xce, 0x40, 0x4a, 0x0e, 0x9b, 0x08, 0x52, - 0xa3, 0xce, 0x89, 0x20, 0x75, 0x32, 0xb5, 0xdf, 0x14, 0x20, 0xaf, 0xe2, 0x2b, 0x12, 0xe4, 0x19, - 0x3e, 0xc9, 0x9b, 0xf8, 0x27, 0x04, 0x97, 0x0d, 0x84, 0x88, 0xb7, 0xc7, 0x66, 0x19, 0xcf, 0xb0, - 0xd6, 0xbb, 0xd3, 0x39, 0x29, 0x84, 0x4d, 0x81, 0x70, 0x1d, 0xdf, 0x31, 0xb5, 0xd1, 0xc8, 0xc6, - 0x1c, 0xff, 0x82, 0x60, 0xd9, 0xcc, 0x99, 0xf8, 0xee, 0x64, 0x10, 0xc6, 0xbb, 0xb8, 0x33, 0xb5, - 0x5f, 0x99, 0x35, 0x18, 0x47, 0xdb, 0x7c, 0xd7, 0x7d, 0x79, 0x52, 0x47, 0xaf, 0x4e, 0xea, 0xe8, - 0xaf, 0x93, 0x3a, 0xfa, 0xfa, 0xb4, 0x3e, 0xf3, 0xea, 0xb4, 0x3e, 0xf3, 0xc7, 0x69, 0x7d, 0xe6, - 0xf3, 0x7b, 0x61, 0x94, 0x1c, 0xa4, 0xbe, 0x13, 0xb0, 0x0e, 0x51, 0x3f, 0x4b, 0x91, 0x1f, 0x6c, - 0x84, 0x8c, 0xf4, 0xb7, 0x49, 0x87, 0xb5, 0xd2, 0x36, 0xe5, 0x32, 0xcf, 0x66, 0x73, 0x43, 0xa5, - 0x4a, 0x0e, 0xbb, 0x94, 0xfb, 0xf3, 0x82, 0xfd, 0xb7, 0xff, 0x09, 0x00, 0x00, 0xff, 0xff, 0xb0, - 0x84, 0x64, 0x2c, 0x98, 0x0d, 0x00, 0x00, + // 1055 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0xcd, 0x6f, 0x1b, 0x45, + 0x14, 0xcf, 0xa4, 0x69, 0xd4, 0x3e, 0xbb, 0x09, 0x9a, 0xe6, 0xc3, 0xdd, 0x16, 0xc7, 0xd9, 0x20, + 0x9a, 0x96, 0x64, 0x27, 0x71, 0x68, 0x12, 0x21, 0x21, 0x41, 0x2a, 0x95, 0xf6, 0x52, 0xca, 0x22, + 0x04, 0x42, 0x42, 0xd1, 0xee, 0x7a, 0xb2, 0x59, 0xc9, 0xde, 0x71, 0x3d, 0xbb, 0x96, 0xa2, 0x2a, + 0x97, 0x9e, 0x10, 0x27, 0x24, 0x24, 0xae, 0x48, 0x1c, 0x39, 0x54, 0x1c, 0x90, 0xb8, 0x72, 0x82, + 0x1c, 0x38, 0x54, 0x82, 0x03, 0x27, 0x8a, 0x12, 0xfe, 0x10, 0xe4, 0x99, 0x59, 0x7b, 0xd7, 0x1e, + 0xd7, 0x6b, 0x14, 0xb8, 0xed, 0xbe, 0xcf, 0xdf, 0xfb, 0xbd, 0xe7, 0xf7, 0xd6, 0x50, 0x0e, 0x5c, + 0x8f, 0x78, 0xac, 0x45, 0x89, 0x57, 0x0f, 0x68, 0x18, 0x91, 0xf6, 0x26, 0x79, 0x1c, 0xd3, 0xd6, + 0x91, 0xd5, 0x6c, 0xb1, 0x88, 0x61, 0x1c, 0xb8, 0x9e, 0xd5, 0xd1, 0x5b, 0x52, 0x6f, 0xb5, 0x37, + 0x8d, 0xdb, 0x1e, 0xe3, 0x0d, 0xc6, 0x89, 0xeb, 0x70, 0x2a, 0x8d, 0x49, 0x7b, 0xd3, 0xa5, 0x91, + 0xb3, 0x49, 0x9a, 0x8e, 0x1f, 0x84, 0x4e, 0x14, 0xb0, 0x50, 0xfa, 0x1b, 0x4b, 0x9a, 0xf8, 0x2a, + 0x92, 0x34, 0xb8, 0xe6, 0x33, 0xe6, 0xd7, 0x29, 0x11, 0x6f, 0x6e, 0x7c, 0x40, 0x9c, 0x50, 0xe5, + 0x36, 0x6e, 0x28, 0x95, 0xd3, 0x0c, 0x88, 0x13, 0x86, 0x2c, 0x12, 0x81, 0xb9, 0xd2, 0xce, 0xf9, + 0xcc, 0x67, 0xe2, 0x91, 0x74, 0x9e, 0xa4, 0xd4, 0xdc, 0x86, 0xc5, 0x0f, 0x3a, 0x88, 0xee, 0x8a, + 0x1c, 0x1f, 0x46, 0x4e, 0x44, 0x6d, 0xfa, 0x38, 0xa6, 0x3c, 0xc2, 0xd7, 0xe1, 0xb2, 0xcc, 0xbc, + 0x1f, 0xd4, 0x4a, 0xa8, 0x82, 0x56, 0x2f, 0xdb, 0x97, 0xa4, 0xe0, 0x41, 0xcd, 0x7c, 0x86, 0xa0, + 0x34, 0xe8, 0xc8, 0x9b, 0x2c, 0xe4, 0x14, 0xef, 0x40, 0x51, 0x79, 0xf2, 0x8e, 0x5c, 0x38, 0x17, + 0xaa, 0x73, 0x96, 0xc4, 0x67, 0x25, 0xd0, 0xad, 0x77, 0xc3, 0x23, 0xbb, 0xe0, 0xf5, 0x02, 0xe0, + 0x39, 0xb8, 0xd8, 0x6c, 0x31, 0x76, 0x50, 0x9a, 0xac, 0xa0, 0xd5, 0xa2, 0x2d, 0x5f, 0xf0, 0x5d, + 0x28, 0x8a, 0x87, 0xfd, 0x43, 0x1a, 0xf8, 0x87, 0x51, 0xe9, 0x82, 0x08, 0x67, 0x58, 0x83, 0x54, + 0x5b, 0xf7, 0x85, 0xc5, 0xde, 0xd4, 0xc9, 0x9f, 0x4b, 0x13, 0x76, 0x41, 0x78, 0x49, 0x91, 0xe9, + 0x0e, 0xe2, 0xe5, 0x49, 0xa5, 0xf7, 0x00, 0x7a, 0x8d, 0x50, 0x68, 0x5f, 0xb7, 0x64, 0xd7, 0xac, + 0x4e, 0xd7, 0x2c, 0xd9, 0x62, 0xd5, 0x35, 0xeb, 0x91, 0xe3, 0x27, 0x2c, 0xd9, 0x29, 0x4f, 0xf3, + 0x77, 0x04, 0xd7, 0x34, 0x49, 0x14, 0x2b, 0x21, 0x5c, 0x49, 0xb3, 0xc2, 0x4b, 0xa8, 0x72, 0x61, + 0xb5, 0x50, 0xbd, 0xa5, 0xab, 0xe3, 0x41, 0x8d, 0x86, 0x51, 0x70, 0x10, 0xd0, 0x5a, 0x2a, 0xd4, + 0x5e, 0xb9, 0x53, 0xd6, 0x77, 0x2f, 0x96, 0x16, 0xb4, 0x6a, 0x6e, 0x17, 0x53, 0x5c, 0x72, 0xfc, + 0x5e, 0xa6, 0xaa, 0x49, 0x51, 0xd5, 0xcd, 0x91, 0x55, 0x49, 0xb0, 0x99, 0xb2, 0xbe, 0x47, 0x60, + 0xc8, 0xb2, 0x3a, 0xaa, 0x90, 0xc7, 0x3c, 0xf7, 0x9c, 0xe0, 0x9b, 0x30, 0xdb, 0xa2, 0xed, 0x80, + 0x07, 0x2c, 0xdc, 0x0f, 0xe3, 0x86, 0x4b, 0x5b, 0x02, 0xc9, 0x94, 0x3d, 0x93, 0x88, 0x1f, 0x0a, + 0x69, 0xc6, 0x30, 0xd5, 0xe7, 0x94, 0xa1, 0x6c, 0x24, 0x5e, 0x81, 0x2b, 0xf5, 0x4e, 0x7d, 0x51, + 0x62, 0x36, 0x55, 0x41, 0xab, 0x97, 0xec, 0xa2, 0x14, 0xaa, 0x6e, 0xff, 0x88, 0xe0, 0xba, 0x16, + 0xb2, 0xea, 0xc5, 0xdb, 0x30, 0xeb, 0x25, 0x9a, 0x1c, 0x43, 0x3a, 0xe3, 0x65, 0xc2, 0xfc, 0x97, + 0x73, 0xfa, 0x54, 0x8f, 0x9c, 0xe7, 0x62, 0xfb, 0x9e, 0xa6, 0xe5, 0xff, 0x66, 0x90, 0x7f, 0x46, + 0x70, 0x43, 0x0f, 0x42, 0xf1, 0xf7, 0x19, 0xbc, 0xd2, 0xc7, 0x5f, 0x32, 0xce, 0x6b, 0xba, 0x72, + 0xb3, 0x61, 0x3e, 0x0e, 0xa2, 0xc3, 0x0c, 0x01, 0xb3, 0x59, 0x7a, 0xcf, 0x71, 0x74, 0x3f, 0x47, + 0xb0, 0xac, 0x29, 0x44, 0x66, 0xff, 0x7f, 0x39, 0xfd, 0x05, 0x81, 0xf9, 0x32, 0x28, 0x8a, 0xd9, + 0x4f, 0x60, 0xb1, 0x8f, 0x59, 0x35, 0x4e, 0x09, 0xc1, 0xa3, 0xe7, 0x69, 0xde, 0xd3, 0x65, 0x38, + 0x3f, 0x52, 0x77, 0x06, 0x56, 0x69, 0x9c, 0x8b, 0x4a, 0x73, 0x6b, 0x60, 0x3d, 0xc6, 0xbd, 0xc2, + 0x17, 0x60, 0x9a, 0x0b, 0x89, 0x72, 0x53, 0x6f, 0xa6, 0x91, 0xc9, 0xf6, 0xc8, 0x69, 0x39, 0x8d, + 0x24, 0x9b, 0xf9, 0x7e, 0x26, 0x60, 0xa2, 0x53, 0x01, 0xab, 0x30, 0xdd, 0x14, 0x12, 0xf5, 0xd3, + 0xd6, 0x12, 0xa7, 0x7c, 0x94, 0xa5, 0xb9, 0x0c, 0x4b, 0x22, 0xe0, 0x47, 0x4d, 0xbf, 0xe5, 0xd4, + 0x32, 0xeb, 0x35, 0xc9, 0x59, 0x87, 0xca, 0x70, 0x13, 0x95, 0xfa, 0x3e, 0xcc, 0xc7, 0x4a, 0xbd, + 0x9f, 0xfb, 0x12, 0x5e, 0x8d, 0x07, 0x23, 0x9a, 0xaf, 0xa9, 0xa1, 0xe9, 0x66, 0xd3, 0xad, 0x60, + 0x33, 0x86, 0x95, 0x97, 0x5a, 0x29, 0x58, 0x0f, 0xa1, 0xd4, 0x83, 0x35, 0xc6, 0xfa, 0x5b, 0x88, + 0xb5, 0x71, 0xab, 0xbf, 0x16, 0xe1, 0xa2, 0xc8, 0x8b, 0xbf, 0x41, 0x50, 0x48, 0xc1, 0xc6, 0x6f, + 0xe8, 0xb8, 0x1e, 0xf2, 0xa1, 0x61, 0xac, 0xe5, 0x33, 0x96, 0x45, 0x98, 0x77, 0x9e, 0xfe, 0xf6, + 0xf7, 0x57, 0x93, 0x04, 0xaf, 0x93, 0xa1, 0x9f, 0x4a, 0x6a, 0x23, 0x91, 0x27, 0xdd, 0x51, 0x3c, + 0xc6, 0x5f, 0x23, 0x28, 0xa6, 0x8f, 0x25, 0xce, 0x95, 0x35, 0x99, 0x34, 0x63, 0x3d, 0xa7, 0xb5, + 0x02, 0x79, 0x4b, 0x80, 0x5c, 0xc1, 0xcb, 0x23, 0x41, 0xe2, 0x17, 0x08, 0x66, 0xb2, 0xbc, 0x62, + 0x6b, 0x78, 0x32, 0x5d, 0xfb, 0x0d, 0x92, 0xdb, 0x5e, 0xc1, 0xab, 0x0b, 0x78, 0x07, 0xb8, 0xa6, + 0x85, 0xd7, 0xb7, 0xd8, 0xd3, 0x34, 0x92, 0xe4, 0x18, 0x93, 0x27, 0x7d, 0x67, 0xfd, 0x98, 0xc8, + 0x35, 0x95, 0x52, 0x48, 0xc1, 0x31, 0x7e, 0x86, 0x60, 0xb6, 0xef, 0x90, 0xe0, 0xbc, 0x90, 0xbb, + 0x0d, 0xd8, 0xc8, 0xef, 0xa0, 0x8a, 0xdc, 0x15, 0x45, 0x56, 0xf1, 0xc6, 0xb8, 0x45, 0xe2, 0x13, + 0x04, 0xf3, 0xda, 0x2d, 0x8d, 0xef, 0xe4, 0x44, 0x91, 0x3d, 0x30, 0xc6, 0xf6, 0xb8, 0x6e, 0xaa, + 0x84, 0x77, 0x44, 0x09, 0x6f, 0xe1, 0xdd, 0xb1, 0xfb, 0xa4, 0x6e, 0x06, 0xfe, 0x36, 0x33, 0xf6, + 0x71, 0xbe, 0xb1, 0x8f, 0xc7, 0x1a, 0xfb, 0xde, 0x0e, 0xcf, 0xfd, 0xdb, 0x8c, 0xb3, 0x7c, 0x7f, + 0xd1, 0x05, 0x29, 0xd7, 0xf1, 0x48, 0x90, 0x99, 0x2b, 0x30, 0x12, 0x64, 0xf6, 0x2e, 0x98, 0xaf, + 0x0a, 0x90, 0x8b, 0x78, 0x5e, 0x82, 0xec, 0xe2, 0x93, 0x27, 0x00, 0xff, 0x80, 0xe0, 0xaa, 0x66, + 0xb7, 0xe3, 0xad, 0xa1, 0x59, 0x86, 0x1f, 0x0b, 0xe3, 0xcd, 0xf1, 0x9c, 0x14, 0xc2, 0xaa, 0x40, + 0xb8, 0x86, 0x6f, 0xeb, 0x68, 0xd4, 0x1e, 0x16, 0x8e, 0x7f, 0x42, 0xb0, 0xa0, 0x5f, 0xff, 0x78, + 0x7b, 0x34, 0x08, 0xed, 0x5a, 0xd9, 0x19, 0xdb, 0x2f, 0xcf, 0x18, 0x0c, 0xbb, 0x40, 0x7c, 0xcf, + 0x3e, 0x39, 0x2d, 0xa3, 0xe7, 0xa7, 0x65, 0xf4, 0xd7, 0x69, 0x19, 0x7d, 0x79, 0x56, 0x9e, 0x78, + 0x7e, 0x56, 0x9e, 0xf8, 0xe3, 0xac, 0x3c, 0xf1, 0xe9, 0xae, 0x1f, 0x44, 0x87, 0xb1, 0x6b, 0x79, + 0xac, 0x41, 0xd4, 0x9f, 0xe9, 0xc0, 0xf5, 0xd6, 0x7d, 0x46, 0xda, 0x5b, 0xa4, 0xc1, 0x6a, 0x71, + 0x9d, 0x72, 0x99, 0x67, 0xa3, 0xba, 0xae, 0x52, 0x45, 0x47, 0x4d, 0xca, 0xdd, 0x69, 0x71, 0xc8, + 0xb6, 0xfe, 0x09, 0x00, 0x00, 0xff, 0xff, 0x42, 0x17, 0xfc, 0x54, 0xb8, 0x0f, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -957,6 +1075,8 @@ type QueryClient interface { // 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. @@ -1011,6 +1131,15 @@ func (c *queryClient) ConsensusStates(ctx context.Context, in *QueryConsensusSta 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...) @@ -1059,6 +1188,8 @@ type QueryServer interface { // 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. @@ -1085,6 +1216,9 @@ func (*UnimplementedQueryServer) ConsensusState(ctx context.Context, req *QueryC 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") } @@ -1174,6 +1308,24 @@ func _Query_ConsensusStates_Handler(srv interface{}, ctx context.Context, dec fu 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 { @@ -1266,6 +1418,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "ConsensusStates", Handler: _Query_ConsensusStates_Handler, }, + { + MethodName: "ConsensusStateHeights", + Handler: _Query_ConsensusStateHeights_Handler, + }, { MethodName: "ClientStatus", Handler: _Query_ClientStatus_Handler, @@ -1646,6 +1802,97 @@ func (m *QueryConsensusStatesResponse) MarshalToSizedBuffer(dAtA []byte) (int, e 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) @@ -2032,6 +2279,42 @@ func (m *QueryConsensusStatesResponse) Size() (n int) { 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 @@ -3102,6 +3385,244 @@ func (m *QueryConsensusStatesResponse) Unmarshal(dAtA []byte) error { } 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 diff --git a/modules/core/02-client/types/query.pb.gw.go b/modules/core/02-client/types/query.pb.gw.go index 3704fb07895..4286cc772ae 100644 --- a/modules/core/02-client/types/query.pb.gw.go +++ b/modules/core/02-client/types/query.pb.gw.go @@ -309,6 +309,78 @@ func local_request_Query_ConsensusStates_0(ctx context.Context, marshaler runtim } +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 @@ -503,6 +575,26 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + 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.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) + 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() @@ -704,6 +796,26 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + 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() @@ -796,6 +908,8 @@ var ( 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(true))) + 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(true))) + 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(true))) pattern_Query_ClientParams_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"ibc", "client", "v1", "params"}, "", runtime.AssumeColonVerbOpt(true))) @@ -814,6 +928,8 @@ var ( forward_Query_ConsensusStates_0 = runtime.ForwardResponseMessage + forward_Query_ConsensusStateHeights_0 = runtime.ForwardResponseMessage + forward_Query_ClientStatus_0 = runtime.ForwardResponseMessage forward_Query_ClientParams_0 = runtime.ForwardResponseMessage diff --git a/modules/core/03-connection/client/utils/utils.go b/modules/core/03-connection/client/utils/utils.go index 3bd54a44541..aaadcf9feae 100644 --- a/modules/core/03-connection/client/utils/utils.go +++ b/modules/core/03-connection/client/utils/utils.go @@ -105,7 +105,6 @@ func queryClientConnectionsABCI(clientCtx client.Context, clientID string) (*typ func QueryConnectionClientState( clientCtx client.Context, connectionID string, prove bool, ) (*types.QueryConnectionClientStateResponse, error) { - queryClient := types.NewQueryClient(clientCtx) req := &types.QueryConnectionClientStateRequest{ ConnectionId: connectionID, @@ -140,7 +139,6 @@ func QueryConnectionClientState( func QueryConnectionConsensusState( clientCtx client.Context, connectionID string, height clienttypes.Height, prove bool, ) (*types.QueryConnectionConsensusStateResponse, error) { - queryClient := types.NewQueryClient(clientCtx) req := &types.QueryConnectionConsensusStateRequest{ ConnectionId: connectionID, diff --git a/modules/core/03-connection/keeper/events.go b/modules/core/03-connection/keeper/events.go index 6b1636ea71e..a8dcd1251a7 100644 --- a/modules/core/03-connection/keeper/events.go +++ b/modules/core/03-connection/keeper/events.go @@ -2,7 +2,7 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" - + "github.com/cosmos/ibc-go/v3/modules/core/03-connection/types" ) diff --git a/modules/core/03-connection/keeper/grpc_query.go b/modules/core/03-connection/keeper/grpc_query.go index 77d8bc98c84..89e9b978e0b 100644 --- a/modules/core/03-connection/keeper/grpc_query.go +++ b/modules/core/03-connection/keeper/grpc_query.go @@ -68,7 +68,6 @@ func (q Keeper) Connections(c context.Context, req *types.QueryConnectionsReques connections = append(connections, &identifiedConnection) return nil }) - if err != nil { return nil, err } @@ -137,7 +136,6 @@ func (q Keeper) ConnectionClientState(c context.Context, req *types.QueryConnect height := clienttypes.GetSelfHeight(ctx) return types.NewQueryConnectionClientStateResponse(identifiedClientState, nil, height), nil - } // ConnectionConsensusState implements the Query/ConnectionConsensusState gRPC method diff --git a/modules/core/03-connection/keeper/grpc_query_test.go b/modules/core/03-connection/keeper/grpc_query_test.go index a2542fe62b2..fe97dd09b85 100644 --- a/modules/core/03-connection/keeper/grpc_query_test.go +++ b/modules/core/03-connection/keeper/grpc_query_test.go @@ -30,13 +30,15 @@ func (suite *KeeperTestSuite) TestQueryConnection() { }, false, }, - {"invalid connectionID", + { + "invalid connectionID", func() { req = &types.QueryConnectionRequest{} }, false, }, - {"connection not found", + { + "connection not found", func() { req = &types.QueryConnectionRequest{ ConnectionId: ibctesting.InvalidID, @@ -186,13 +188,15 @@ func (suite *KeeperTestSuite) TestQueryClientConnections() { }, false, }, - {"invalid connectionID", + { + "invalid connectionID", func() { req = &types.QueryClientConnectionsRequest{} }, false, }, - {"connection not found", + { + "connection not found", func() { req = &types.QueryClientConnectionsRequest{ ClientId: ibctesting.InvalidID, diff --git a/modules/core/03-connection/keeper/handshake.go b/modules/core/03-connection/keeper/handshake.go index 177324e3e17..fac9e79971d 100644 --- a/modules/core/03-connection/keeper/handshake.go +++ b/modules/core/03-connection/keeper/handshake.go @@ -59,8 +59,8 @@ func (k Keeper) ConnOpenInit( // code is executed on chain B). // // NOTE: -// - Here chain A acts as the counterparty -// - Identifiers are checked on msg validation +// - Here chain A acts as the counterparty +// - Identifiers are checked on msg validation func (k Keeper) ConnOpenTry( ctx sdk.Context, previousConnectionID string, // previousIdentifier diff --git a/modules/core/03-connection/keeper/handshake_test.go b/modules/core/03-connection/keeper/handshake_test.go index 6f5dfbce1d9..8707b72d401 100644 --- a/modules/core/03-connection/keeper/handshake_test.go +++ b/modules/core/03-connection/keeper/handshake_test.go @@ -370,7 +370,6 @@ func (suite *KeeperTestSuite) TestConnOpenAck() { tmClient.ChainId = "wrongchainid" suite.chainB.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainB.GetContext(), path.EndpointB.ClientID, tmClient) - }, false}, {"consensus height >= latest height", func() { err := path.EndpointA.ConnOpenInit() @@ -554,7 +553,6 @@ func (suite *KeeperTestSuite) TestConnOpenAck() { err = path.EndpointB.ConnOpenTry() suite.Require().NoError(err) - }, false}, } diff --git a/modules/core/03-connection/keeper/keeper_test.go b/modules/core/03-connection/keeper/keeper_test.go index 88610201946..410c2ce99ab 100644 --- a/modules/core/03-connection/keeper/keeper_test.go +++ b/modules/core/03-connection/keeper/keeper_test.go @@ -45,7 +45,6 @@ func (suite *KeeperTestSuite) TestSetAndGetConnection() { } func (suite *KeeperTestSuite) TestSetAndGetClientConnectionPaths() { - path := ibctesting.NewPath(suite.chainA, suite.chainB) suite.coordinator.SetupClients(path) diff --git a/modules/core/03-connection/types/codec.go b/modules/core/03-connection/types/codec.go index 8f65884aadf..96ae3cba865 100644 --- a/modules/core/03-connection/types/codec.go +++ b/modules/core/03-connection/types/codec.go @@ -38,11 +38,9 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) { msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) } -var ( - // 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. - SubModuleCdc = codec.NewProtoCodec(codectypes.NewInterfaceRegistry()) -) +// 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/genesis_test.go b/modules/core/03-connection/types/genesis_test.go index db2192385b7..31906350531 100644 --- a/modules/core/03-connection/types/genesis_test.go +++ b/modules/core/03-connection/types/genesis_test.go @@ -11,7 +11,6 @@ import ( ) func TestValidateGenesis(t *testing.T) { - testCases := []struct { name string genState types.GenesisState diff --git a/modules/core/03-connection/types/msgs.go b/modules/core/03-connection/types/msgs.go index 3ea63c84d37..fb879a4e2ec 100644 --- a/modules/core/03-connection/types/msgs.go +++ b/modules/core/03-connection/types/msgs.go @@ -23,6 +23,7 @@ var ( // NewMsgConnectionOpenInit creates a new MsgConnectionOpenInit instance. It sets the // counterparty connection identifier to be empty. +// //nolint:interfacer func NewMsgConnectionOpenInit( clientID, counterpartyClientID string, @@ -72,6 +73,7 @@ func (msg MsgConnectionOpenInit) GetSigners() []sdk.AccAddress { } // NewMsgConnectionOpenTry creates a new MsgConnectionOpenTry instance +// //nolint:interfacer func NewMsgConnectionOpenTry( previousConnectionID, clientID, counterpartyConnectionID, @@ -169,6 +171,7 @@ func (msg MsgConnectionOpenTry) GetSigners() []sdk.AccAddress { } // NewMsgConnectionOpenAck creates a new MsgConnectionOpenAck instance +// //nolint:interfacer func NewMsgConnectionOpenAck( connectionID, counterpartyConnectionID string, counterpartyClient exported.ClientState, @@ -250,6 +253,7 @@ func (msg MsgConnectionOpenAck) GetSigners() []sdk.AccAddress { } // NewMsgConnectionOpenConfirm creates a new MsgConnectionOpenConfirm instance +// //nolint:interfacer func NewMsgConnectionOpenConfirm( connectionID string, proofAck []byte, proofHeight clienttypes.Height, diff --git a/modules/core/03-connection/types/msgs_test.go b/modules/core/03-connection/types/msgs_test.go index 9cf741bb2c0..9d279a902c7 100644 --- a/modules/core/03-connection/types/msgs_test.go +++ b/modules/core/03-connection/types/msgs_test.go @@ -10,6 +10,7 @@ import ( storetypes "github.com/cosmos/cosmos-sdk/store/types" "github.com/stretchr/testify/suite" abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" dbm "github.com/tendermint/tm-db" clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" @@ -46,7 +47,7 @@ func (suite *MsgTestSuite) SetupTest() { app := simapp.Setup(false) db := dbm.NewMemDB() - store := rootmulti.NewStore(db) + store := rootmulti.NewStore(db, log.NewNopLogger()) storeKey := storetypes.NewKVStoreKey("iavlStoreKey") store.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, nil) @@ -68,7 +69,6 @@ func (suite *MsgTestSuite) SetupTest() { suite.Require().NoError(err) suite.proof = proof - } func TestMsgTestSuite(t *testing.T) { @@ -81,7 +81,7 @@ func (suite *MsgTestSuite) TestNewMsgConnectionOpenInit() { // will be used in protocol. var version *types.Version - var testCases = []struct { + testCases := []struct { name string msg *types.MsgConnectionOpenInit expPass bool @@ -124,7 +124,7 @@ func (suite *MsgTestSuite) TestNewMsgConnectionOpenTry() { chainID, ibctmtypes.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clienttypes.ZeroHeight(), commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath, false, false, ) - var testCases = []struct { + testCases := []struct { name string msg *types.MsgConnectionOpenTry expPass bool @@ -176,7 +176,7 @@ func (suite *MsgTestSuite) TestNewMsgConnectionOpenAck() { ) connectionID := "connection-0" - var testCases = []struct { + testCases := []struct { name string msg *types.MsgConnectionOpenAck expPass bool @@ -215,7 +215,7 @@ func (suite *MsgTestSuite) TestNewMsgConnectionOpenConfirm() { types.NewMsgConnectionOpenConfirm(connectionID, suite.proof, clientHeight, signer), } - var testCases = []struct { + testCases := []struct { msg *types.MsgConnectionOpenConfirm expPass bool errMsg string diff --git a/modules/core/03-connection/types/version_test.go b/modules/core/03-connection/types/version_test.go index 3fa637cc4de..404f4f8ca56 100644 --- a/modules/core/03-connection/types/version_test.go +++ b/modules/core/03-connection/types/version_test.go @@ -141,7 +141,6 @@ func TestVerifyProposedVersion(t *testing.T) { require.Error(t, err, "test case %d: %s", i, tc.name) } } - } func TestVerifySupportedFeature(t *testing.T) { diff --git a/modules/core/04-channel/client/utils/utils.go b/modules/core/04-channel/client/utils/utils.go index de40903e6fb..4124fe9b2b4 100644 --- a/modules/core/04-channel/client/utils/utils.go +++ b/modules/core/04-channel/client/utils/utils.go @@ -64,7 +64,6 @@ func queryChannelABCI(clientCtx client.Context, portID, channelID string) (*type func QueryChannelClientState( clientCtx client.Context, portID, channelID string, prove bool, ) (*types.QueryChannelClientStateResponse, error) { - queryClient := types.NewQueryClient(clientCtx) req := &types.QueryChannelClientStateRequest{ PortId: portID, @@ -99,7 +98,6 @@ func QueryChannelClientState( 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, diff --git a/modules/core/04-channel/keeper/events.go b/modules/core/04-channel/keeper/events.go index 37f9cd6ed63..66b47467216 100644 --- a/modules/core/04-channel/keeper/events.go +++ b/modules/core/04-channel/keeper/events.go @@ -20,6 +20,7 @@ func EmitChannelOpenInitEvent(ctx sdk.Context, portID string, channelID string, sdk.NewAttribute(types.AttributeCounterpartyPortID, channel.Counterparty.PortId), sdk.NewAttribute(types.AttributeCounterpartyChannelID, channel.Counterparty.ChannelId), sdk.NewAttribute(types.AttributeKeyConnectionID, channel.ConnectionHops[0]), + sdk.NewAttribute(types.AttributeVersion, channel.Version), ), }) @@ -41,6 +42,7 @@ func EmitChannelOpenTryEvent(ctx sdk.Context, portID string, channelID string, c sdk.NewAttribute(types.AttributeCounterpartyPortID, channel.Counterparty.PortId), sdk.NewAttribute(types.AttributeCounterpartyChannelID, channel.Counterparty.ChannelId), sdk.NewAttribute(types.AttributeKeyConnectionID, channel.ConnectionHops[0]), + sdk.NewAttribute(types.AttributeVersion, channel.Version), ), }) ctx.EventManager().EmitEvents(sdk.Events{ diff --git a/modules/core/04-channel/keeper/grpc_query.go b/modules/core/04-channel/keeper/grpc_query.go index 6bd8418a564..6d4b150ada9 100644 --- a/modules/core/04-channel/keeper/grpc_query.go +++ b/modules/core/04-channel/keeper/grpc_query.go @@ -69,7 +69,6 @@ func (q Keeper) Channels(c context.Context, req *types.QueryChannelsRequest) (*t channels = append(channels, &identifiedChannel) return nil }) - if err != nil { return nil, err } @@ -118,7 +117,6 @@ func (q Keeper) ConnectionChannels(c context.Context, req *types.QueryConnection channels = append(channels, &identifiedChannel) return nil }) - if err != nil { return nil, err } @@ -252,7 +250,6 @@ func (q Keeper) PacketCommitments(c context.Context, req *types.QueryPacketCommi commitments = append(commitments, &commitment) return nil }) - if err != nil { return nil, err } @@ -361,7 +358,6 @@ func (q Keeper) PacketAcknowledgements(c context.Context, req *types.QueryPacket return nil }) - if err != nil { return nil, err } @@ -401,7 +397,7 @@ func (q Keeper) UnreceivedPackets(c context.Context, req *types.QueryUnreceivedP ctx := sdk.UnwrapSDKContext(c) - var unreceivedSequences = []uint64{} + unreceivedSequences := []uint64{} for i, seq := range req.PacketCommitmentSequences { if seq == 0 { @@ -450,7 +446,7 @@ func (q Keeper) UnreceivedAcks(c context.Context, req *types.QueryUnreceivedAcks ctx := sdk.UnwrapSDKContext(c) - var unreceivedSequences = []uint64{} + unreceivedSequences := []uint64{} for i, seq := range req.PacketAckSequences { if seq == 0 { diff --git a/modules/core/04-channel/keeper/grpc_query_test.go b/modules/core/04-channel/keeper/grpc_query_test.go index cc899e99444..64c81f2105d 100644 --- a/modules/core/04-channel/keeper/grpc_query_test.go +++ b/modules/core/04-channel/keeper/grpc_query_test.go @@ -51,7 +51,8 @@ func (suite *KeeperTestSuite) TestQueryChannel() { }, false, }, - {"channel not found", + { + "channel not found", func() { req = &types.QueryChannelRequest{ PortId: "test-port-id", @@ -628,7 +629,8 @@ func (suite *KeeperTestSuite) TestQueryPacketCommitment() { }, false, }, - {"invalid sequence", + { + "invalid sequence", func() { req = &types.QueryPacketCommitmentRequest{ PortId: "test-port-id", @@ -638,7 +640,8 @@ func (suite *KeeperTestSuite) TestQueryPacketCommitment() { }, false, }, - {"channel not found", + { + "channel not found", func() { req = &types.QueryPacketCommitmentRequest{ PortId: "test-port-id", @@ -819,7 +822,8 @@ func (suite *KeeperTestSuite) TestQueryPacketReceipt() { }, false, }, - {"invalid sequence", + { + "invalid sequence", func() { req = &types.QueryPacketReceiptRequest{ PortId: "test-port-id", @@ -923,7 +927,8 @@ func (suite *KeeperTestSuite) TestQueryPacketAcknowledgement() { }, false, }, - {"invalid sequence", + { + "invalid sequence", func() { req = &types.QueryPacketAcknowledgementRequest{ PortId: "test-port-id", @@ -933,7 +938,8 @@ func (suite *KeeperTestSuite) TestQueryPacketAcknowledgement() { }, false, }, - {"channel not found", + { + "channel not found", func() { req = &types.QueryPacketAcknowledgementRequest{ PortId: "test-port-id", @@ -1400,7 +1406,8 @@ func (suite *KeeperTestSuite) TestQueryNextSequenceReceive() { }, false, }, - {"channel not found", + { + "channel not found", func() { req = &types.QueryNextSequenceReceiveRequest{ PortId: "test-port-id", diff --git a/modules/core/04-channel/keeper/handshake.go b/modules/core/04-channel/keeper/handshake.go index 1d650bb4d7b..022dea88281 100644 --- a/modules/core/04-channel/keeper/handshake.go +++ b/modules/core/04-channel/keeper/handshake.go @@ -339,7 +339,7 @@ func (k Keeper) WriteOpenAckChannel( } // ChanOpenConfirm is called by the counterparty module to close their end of the -// channel, since the other end has been closed. +// channel, since the other end has been closed. func (k Keeper) ChanOpenConfirm( ctx sdk.Context, portID, diff --git a/modules/core/04-channel/keeper/packet_test.go b/modules/core/04-channel/keeper/packet_test.go index db6cce545c9..4d05df46533 100644 --- a/modules/core/04-channel/keeper/packet_test.go +++ b/modules/core/04-channel/keeper/packet_test.go @@ -220,7 +220,6 @@ func (suite *KeeperTestSuite) TestSendPacket() { } }) } - } // TestRecvPacket test RecvPacket on chainB. Since packet commitment verification will always @@ -487,7 +486,6 @@ func (suite *KeeperTestSuite) TestRecvPacket() { } }) } - } func (suite *KeeperTestSuite) TestWriteAcknowledgement() { diff --git a/modules/core/04-channel/keeper/timeout_test.go b/modules/core/04-channel/keeper/timeout_test.go index a7151c4c0ff..ae3a816ef16 100644 --- a/modules/core/04-channel/keeper/timeout_test.go +++ b/modules/core/04-channel/keeper/timeout_test.go @@ -430,5 +430,4 @@ func (suite *KeeperTestSuite) TestTimeoutOnClose() { } }) } - } diff --git a/modules/core/04-channel/types/events.go b/modules/core/04-channel/types/events.go index 863b6c231cd..8740c3838eb 100644 --- a/modules/core/04-channel/types/events.go +++ b/modules/core/04-channel/types/events.go @@ -11,6 +11,7 @@ const ( AttributeKeyConnectionID = "connection_id" AttributeKeyPortID = "port_id" AttributeKeyChannelID = "channel_id" + AttributeVersion = "version" AttributeCounterpartyPortID = "counterparty_port_id" AttributeCounterpartyChannelID = "counterparty_channel_id" diff --git a/modules/core/04-channel/types/msgs_test.go b/modules/core/04-channel/types/msgs_test.go index 623ed992d22..c7e2497e8fc 100644 --- a/modules/core/04-channel/types/msgs_test.go +++ b/modules/core/04-channel/types/msgs_test.go @@ -10,6 +10,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/suite" abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" dbm "github.com/tendermint/tm-db" clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" @@ -77,7 +78,7 @@ type TypesTestSuite struct { func (suite *TypesTestSuite) SetupTest() { app := simapp.Setup(false) db := dbm.NewMemDB() - store := rootmulti.NewStore(db) + store := rootmulti.NewStore(db, log.NewNopLogger()) storeKey := storetypes.NewKVStoreKey("iavlStoreKey") store.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, nil) diff --git a/modules/core/04-channel/types/tx.pb.go b/modules/core/04-channel/types/tx.pb.go index 8f9ebe6ec63..fe6f344384d 100644 --- a/modules/core/04-channel/types/tx.pb.go +++ b/modules/core/04-channel/types/tx.pb.go @@ -105,6 +105,7 @@ 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" yaml:"channel_id"` + Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` } func (m *MsgChannelOpenInitResponse) Reset() { *m = MsgChannelOpenInitResponse{} } @@ -147,6 +148,13 @@ func (m *MsgChannelOpenInitResponse) GetChannelId() string { return "" } +func (m *MsgChannelOpenInitResponse) GetVersion() string { + if m != nil { + return m.Version + } + return "" +} + // 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. @@ -198,6 +206,7 @@ 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"` } func (m *MsgChannelOpenTryResponse) Reset() { *m = MsgChannelOpenTryResponse{} } @@ -233,6 +242,13 @@ func (m *MsgChannelOpenTryResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgChannelOpenTryResponse proto.InternalMessageInfo +func (m *MsgChannelOpenTryResponse) GetVersion() string { + if m != nil { + return m.Version + } + return "" +} + // 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 { @@ -902,87 +918,88 @@ func init() { func init() { proto.RegisterFile("ibc/core/channel/v1/tx.proto", fileDescriptor_bc4637e0ac3fc7b7) } var fileDescriptor_bc4637e0ac3fc7b7 = []byte{ - // 1275 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0xcd, 0x6e, 0xdb, 0x46, - 0x10, 0xd6, 0x5f, 0x64, 0x67, 0xe4, 0xda, 0x32, 0x65, 0x3b, 0x32, 0x65, 0x8b, 0x2a, 0x0f, 0xb1, - 0xe1, 0xc2, 0x52, 0x6c, 0xa7, 0x28, 0x62, 0x14, 0x28, 0x2c, 0x55, 0x41, 0x8d, 0xd6, 0x3f, 0xa0, - 0xe4, 0x02, 0x75, 0x8b, 0x0a, 0x12, 0xb5, 0x91, 0x09, 0x49, 0x5c, 0x95, 0xa4, 0x94, 0xe8, 0x0d, - 0x02, 0x9f, 0x72, 0x0e, 0x60, 0x20, 0x45, 0x4f, 0x45, 0x0f, 0xe9, 0x63, 0xe4, 0x98, 0x53, 0x5b, - 0xf4, 0x20, 0x14, 0xf6, 0xa5, 0x67, 0x3d, 0x41, 0xc1, 0xe5, 0x92, 0xa2, 0x24, 0x12, 0xa6, 0x13, - 0xdb, 0xcd, 0x6d, 0x77, 0xe6, 0xdb, 0xd9, 0xd9, 0xef, 0x1b, 0xee, 0x0f, 0x61, 0x49, 0xaa, 0x88, - 0x19, 0x11, 0x2b, 0x28, 0x23, 0x9e, 0x94, 0x65, 0x19, 0x35, 0x32, 0x9d, 0x8d, 0x8c, 0xf6, 0x2c, - 0xdd, 0x52, 0xb0, 0x86, 0x99, 0x98, 0x54, 0x11, 0xd3, 0xba, 0x37, 0x4d, 0xbd, 0xe9, 0xce, 0x06, - 0x3b, 0x57, 0xc3, 0x35, 0x4c, 0xfc, 0x19, 0xbd, 0x65, 0x40, 0x59, 0x6e, 0x10, 0xa8, 0x21, 0x21, - 0x59, 0xd3, 0xe3, 0x18, 0x2d, 0x0a, 0xf8, 0xd8, 0x69, 0x26, 0x33, 0x2c, 0x81, 0xf0, 0x3f, 0xfb, - 0x81, 0xd9, 0x53, 0x6b, 0x39, 0xc3, 0x78, 0xd0, 0x42, 0xf2, 0xae, 0x2c, 0x69, 0xcc, 0x27, 0x30, - 0xd1, 0xc2, 0x8a, 0x56, 0x92, 0xaa, 0x71, 0x7f, 0xca, 0xbf, 0x7a, 0x37, 0xcb, 0xf4, 0x7b, 0xdc, - 0x74, 0xb7, 0xdc, 0x6c, 0x6c, 0xf3, 0xd4, 0xc1, 0x0b, 0x61, 0xbd, 0xb5, 0x5b, 0x65, 0x3e, 0x87, - 0x09, 0x1a, 0x34, 0x1e, 0x48, 0xf9, 0x57, 0x23, 0x9b, 0x4b, 0x69, 0x87, 0x45, 0xa4, 0xe9, 0x1c, - 0xd9, 0xd0, 0x9b, 0x1e, 0xe7, 0x13, 0xcc, 0x21, 0xcc, 0x02, 0x84, 0x55, 0xa9, 0x26, 0x23, 0x25, - 0x1e, 0xd4, 0x67, 0x12, 0x68, 0x6f, 0x7b, 0xf2, 0xf9, 0x2b, 0xce, 0xf7, 0xef, 0x2b, 0xce, 0xc7, - 0x0b, 0xc0, 0x8e, 0xa7, 0x28, 0x20, 0xb5, 0x85, 0x65, 0x15, 0x31, 0x0f, 0x01, 0x68, 0xa8, 0x41, - 0xb6, 0xf3, 0xfd, 0x1e, 0x37, 0x6b, 0x64, 0x3b, 0xf0, 0xf1, 0xc2, 0x5d, 0xda, 0xd9, 0xad, 0xf2, - 0x7f, 0x04, 0x61, 0x76, 0x38, 0x68, 0x51, 0xe9, 0x5e, 0x6d, 0xd9, 0xfb, 0x10, 0x6b, 0x29, 0xa8, - 0x23, 0xe1, 0xb6, 0x5a, 0xb2, 0x65, 0x10, 0x20, 0x03, 0x93, 0xfd, 0x1e, 0xc7, 0xd2, 0x81, 0xe3, - 0x20, 0x5e, 0x98, 0x35, 0xad, 0x39, 0x33, 0x25, 0x3b, 0x8d, 0xc1, 0xab, 0xd3, 0x28, 0xc0, 0x9c, - 0x88, 0xdb, 0xb2, 0x86, 0x94, 0x56, 0x59, 0xd1, 0xba, 0xa5, 0x0e, 0x52, 0x54, 0x09, 0xcb, 0xf1, - 0x10, 0x49, 0x87, 0xeb, 0xf7, 0xb8, 0x04, 0x25, 0xc4, 0x01, 0xc5, 0x0b, 0x31, 0xbb, 0xf9, 0x5b, - 0xc3, 0xaa, 0x53, 0xdb, 0x52, 0x30, 0x7e, 0x52, 0x92, 0x64, 0x49, 0x8b, 0xdf, 0x49, 0xf9, 0x57, - 0xa7, 0xec, 0xd4, 0x0e, 0x7c, 0xbc, 0x70, 0x97, 0x74, 0x48, 0xed, 0x1c, 0xc3, 0x94, 0xe1, 0x39, - 0x41, 0x52, 0xed, 0x44, 0x8b, 0x87, 0xc9, 0x62, 0x58, 0xdb, 0x62, 0x8c, 0x1a, 0xed, 0x6c, 0xa4, - 0xbf, 0x22, 0x88, 0x6c, 0x42, 0x5f, 0x4a, 0xbf, 0xc7, 0xc5, 0xec, 0x71, 0x8d, 0xd1, 0xbc, 0x10, - 0x21, 0x5d, 0x03, 0x69, 0x2b, 0x96, 0x09, 0x97, 0x62, 0x49, 0xc0, 0xe2, 0x98, 0xae, 0x66, 0xad, - 0xf0, 0x7f, 0x8e, 0xa9, 0xbe, 0x23, 0xd6, 0xaf, 0xa6, 0xfa, 0x70, 0xb9, 0x05, 0xbc, 0x95, 0x1b, - 0x73, 0x0c, 0xf7, 0x86, 0x78, 0xb7, 0x85, 0x20, 0x55, 0x9f, 0xe5, 0xfb, 0x3d, 0x2e, 0xe9, 0x20, - 0x90, 0x3d, 0xde, 0xbc, 0xdd, 0x33, 0xa8, 0x9b, 0x9b, 0x50, 0x7e, 0x03, 0x0c, 0x41, 0x4b, 0x9a, - 0xd2, 0xa5, 0xc2, 0xcf, 0xf5, 0x7b, 0x5c, 0xd4, 0x2e, 0x90, 0xa6, 0x74, 0x79, 0x61, 0x92, 0xb4, - 0xf5, 0x6f, 0xe7, 0x03, 0x93, 0x7d, 0x47, 0xac, 0x5b, 0xb2, 0xff, 0x16, 0x80, 0xf9, 0x61, 0x6f, - 0x0e, 0xcb, 0x4f, 0x24, 0xa5, 0x79, 0x1b, 0xd2, 0x5b, 0x54, 0x96, 0xc5, 0x3a, 0x11, 0xdb, 0x81, - 0xca, 0xb2, 0x58, 0x37, 0xa9, 0xd4, 0x0b, 0x72, 0x94, 0xca, 0xd0, 0x8d, 0x50, 0x79, 0xc7, 0x85, - 0x4a, 0x0e, 0x96, 0x1d, 0xc9, 0xb2, 0xe8, 0x7c, 0xe9, 0x87, 0xd8, 0x00, 0x91, 0x6b, 0x60, 0x15, - 0x5d, 0xfd, 0xd0, 0x78, 0x37, 0x32, 0x2f, 0x3f, 0x2c, 0x96, 0x21, 0xe1, 0x90, 0x9b, 0x95, 0xfb, - 0xeb, 0x00, 0x2c, 0x8c, 0xf8, 0x6f, 0xb1, 0x16, 0x86, 0x37, 0xd4, 0xe0, 0x3b, 0x6e, 0xa8, 0xb7, - 0x5b, 0x0e, 0x29, 0x48, 0x3a, 0x13, 0x66, 0x71, 0xfa, 0x22, 0x00, 0x1f, 0xed, 0xa9, 0x35, 0x01, - 0x89, 0x9d, 0xc3, 0xb2, 0x58, 0x47, 0x1a, 0xf3, 0x08, 0xc2, 0x2d, 0xd2, 0x22, 0x4c, 0x46, 0x36, - 0x13, 0x8e, 0x27, 0x99, 0x01, 0xa6, 0x07, 0x19, 0x1d, 0xc0, 0x3c, 0x86, 0xa8, 0x91, 0xae, 0x88, - 0x9b, 0x4d, 0x49, 0x6b, 0x22, 0x59, 0x23, 0xf4, 0x4e, 0x65, 0x13, 0xfd, 0x1e, 0x77, 0xcf, 0xbe, - 0xa0, 0x01, 0x82, 0x17, 0x66, 0x88, 0x29, 0x67, 0x59, 0xc6, 0x48, 0x0b, 0xde, 0x08, 0x69, 0x21, - 0x17, 0xd2, 0x7e, 0x24, 0x1b, 0xce, 0x80, 0x11, 0xeb, 0xb6, 0xf2, 0x05, 0x84, 0x15, 0xa4, 0xb6, - 0x1b, 0x06, 0x33, 0xd3, 0x9b, 0x2b, 0x8e, 0xcc, 0x98, 0x70, 0x81, 0x40, 0x8b, 0xdd, 0x16, 0x12, - 0xe8, 0xb0, 0xed, 0x90, 0x3e, 0x07, 0xff, 0x77, 0x00, 0x60, 0x4f, 0xad, 0x15, 0xa5, 0x26, 0xc2, - 0xed, 0xeb, 0xe1, 0xbb, 0x2d, 0x2b, 0x48, 0x44, 0x52, 0x07, 0x55, 0xdd, 0xf8, 0x1e, 0x20, 0x4c, - 0xbe, 0x8f, 0x2c, 0xcb, 0x8d, 0xf2, 0xfd, 0x35, 0x30, 0x32, 0x7a, 0xa6, 0x95, 0x54, 0xf4, 0x53, - 0x1b, 0xc9, 0x22, 0x2a, 0x29, 0x48, 0xec, 0x10, 0xee, 0x43, 0xd9, 0xe5, 0x7e, 0x8f, 0x5b, 0x34, - 0x22, 0x8c, 0x63, 0x78, 0x21, 0xaa, 0x1b, 0x0b, 0xd4, 0xa6, 0xeb, 0xe1, 0xa1, 0xe2, 0xbf, 0x27, - 0x57, 0x62, 0xca, 0xed, 0x75, 0x2b, 0xf7, 0xd2, 0xb8, 0x82, 0xd0, 0xe8, 0x07, 0x32, 0xf9, 0xa2, - 0x3e, 0x04, 0x01, 0x3f, 0x83, 0x08, 0xfd, 0xac, 0xf4, 0x8c, 0xe8, 0xe6, 0xb4, 0xd0, 0xef, 0x71, - 0xcc, 0xd0, 0x37, 0xa7, 0x3b, 0x79, 0xc1, 0xd8, 0xc6, 0x8c, 0xdc, 0x6f, 0x72, 0x7b, 0x72, 0x56, - 0xfe, 0xce, 0xfb, 0x2a, 0x1f, 0x76, 0x51, 0xbe, 0x42, 0x6e, 0x11, 0xc3, 0xda, 0x5c, 0x77, 0x01, - 0xfc, 0x1e, 0x20, 0xe5, 0xb5, 0x23, 0xd6, 0x65, 0xfc, 0xb4, 0x81, 0xaa, 0x35, 0x44, 0xf6, 0xab, - 0xf7, 0xa8, 0x80, 0x55, 0x98, 0x29, 0x0f, 0x47, 0x33, 0x0a, 0x40, 0x18, 0x35, 0x0f, 0x34, 0xd6, - 0x07, 0x56, 0xdd, 0x34, 0x26, 0x4e, 0x53, 0xe3, 0x1d, 0xbd, 0xf3, 0x3f, 0x1f, 0x41, 0x22, 0x79, - 0x00, 0x8e, 0x30, 0x76, 0xcd, 0xba, 0xac, 0xfd, 0xea, 0x07, 0x66, 0x1c, 0xc4, 0x7c, 0x0a, 0x29, - 0x21, 0x5f, 0x38, 0x3c, 0xd8, 0x2f, 0xe4, 0x4b, 0x42, 0xbe, 0x70, 0xf4, 0x4d, 0xb1, 0x54, 0xfc, - 0xee, 0x30, 0x5f, 0x3a, 0xda, 0x2f, 0x1c, 0xe6, 0x73, 0xbb, 0x8f, 0x77, 0xf3, 0x5f, 0x46, 0x7d, - 0xec, 0xcc, 0xe9, 0x59, 0x2a, 0x62, 0x33, 0x31, 0x2b, 0xb0, 0xe8, 0x38, 0x6c, 0xff, 0xe0, 0xe0, - 0x30, 0xea, 0x67, 0x27, 0x4f, 0xcf, 0x52, 0x21, 0xbd, 0xcd, 0xac, 0xc3, 0x92, 0x23, 0xb0, 0x70, - 0x94, 0xcb, 0xe5, 0x0b, 0x85, 0x68, 0x80, 0x8d, 0x9c, 0x9e, 0xa5, 0x26, 0x68, 0x97, 0x0d, 0x3d, - 0xff, 0x25, 0xe9, 0xdb, 0x7c, 0x3d, 0x09, 0xc1, 0x3d, 0xb5, 0xc6, 0xd4, 0x61, 0x66, 0xf4, 0xe5, - 0xee, 0xbc, 0xfa, 0xf1, 0xf7, 0x33, 0x9b, 0xf1, 0x08, 0xb4, 0x78, 0x3e, 0x81, 0xe9, 0x91, 0xe7, - 0xf2, 0x7d, 0x0f, 0x21, 0x8a, 0x4a, 0x97, 0x4d, 0x7b, 0xc3, 0xb9, 0xcc, 0xa4, 0xdf, 0x88, 0xbd, - 0xcc, 0xb4, 0x23, 0xd6, 0x3d, 0xcd, 0x64, 0x7b, 0x19, 0x30, 0x1a, 0x30, 0x0e, 0xaf, 0x82, 0x35, - 0x0f, 0x51, 0x28, 0x96, 0xdd, 0xf4, 0x8e, 0xb5, 0x66, 0x95, 0x21, 0x3a, 0x76, 0x79, 0x5e, 0xbd, - 0x24, 0x8e, 0x85, 0x64, 0x1f, 0x78, 0x45, 0x5a, 0xf3, 0x3d, 0x85, 0x98, 0xe3, 0x85, 0xd7, 0x4b, - 0x20, 0x73, 0x9d, 0x5b, 0x57, 0x00, 0x5b, 0x13, 0xff, 0x00, 0x60, 0xbb, 0x15, 0xf2, 0x6e, 0x21, - 0x06, 0x18, 0x76, 0xed, 0x72, 0x8c, 0x15, 0xbd, 0x00, 0x13, 0xe6, 0x05, 0x88, 0x73, 0x1b, 0x46, - 0x01, 0xec, 0xca, 0x25, 0x00, 0x7b, 0xed, 0x8d, 0x9c, 0xcd, 0xf7, 0x2f, 0x19, 0x4a, 0x71, 0xee, - 0xb5, 0xe7, 0x72, 0x9e, 0xd4, 0x61, 0x66, 0xf4, 0x10, 0x70, 0xcd, 0x72, 0x04, 0xe8, 0xfe, 0xf1, - 0xba, 0x6c, 0x92, 0xd9, 0xc2, 0x9b, 0xf3, 0xa4, 0xff, 0xed, 0x79, 0xd2, 0xff, 0xcf, 0x79, 0xd2, - 0xff, 0xe2, 0x22, 0xe9, 0x7b, 0x7b, 0x91, 0xf4, 0xfd, 0x75, 0x91, 0xf4, 0x1d, 0x3f, 0xaa, 0x49, - 0xda, 0x49, 0xbb, 0x92, 0x16, 0x71, 0x33, 0x23, 0x62, 0xb5, 0x89, 0xd5, 0x8c, 0x54, 0x11, 0xd7, - 0x6b, 0x38, 0xd3, 0xd9, 0xca, 0x34, 0x71, 0xb5, 0xdd, 0x40, 0xaa, 0xf1, 0x13, 0xf1, 0xc1, 0xc3, - 0x75, 0xf3, 0x3f, 0xa2, 0xd6, 0x6d, 0x21, 0xb5, 0x12, 0x26, 0xff, 0x10, 0xb7, 0xfe, 0x0b, 0x00, - 0x00, 0xff, 0xff, 0xec, 0xba, 0x10, 0x62, 0xd2, 0x14, 0x00, 0x00, + // 1294 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0x4d, 0x6f, 0xe2, 0x56, + 0x17, 0xc6, 0x40, 0x20, 0x39, 0xe4, 0x4d, 0x88, 0x49, 0x32, 0xc4, 0x24, 0x98, 0xd7, 0x8b, 0x49, + 0x94, 0x2a, 0x30, 0x49, 0x66, 0x54, 0x4d, 0x54, 0xa9, 0x0a, 0x94, 0x51, 0xa3, 0x36, 0x1f, 0x32, + 0xa4, 0x52, 0xd3, 0xaa, 0x08, 0xcc, 0x1d, 0x62, 0x01, 0x36, 0xb5, 0x0d, 0x33, 0xfc, 0x83, 0x51, + 0x56, 0xb3, 0x1e, 0x29, 0xd2, 0x54, 0x5d, 0x55, 0x5d, 0x4c, 0x7f, 0xc6, 0x2c, 0x67, 0xd5, 0x56, + 0x5d, 0xa0, 0x2a, 0xd9, 0x74, 0xcd, 0x2f, 0xa8, 0x7c, 0x7d, 0x6d, 0x0c, 0xd8, 0x8a, 0x33, 0x93, + 0x64, 0xba, 0xf3, 0xbd, 0xe7, 0xb9, 0xe7, 0x9c, 0xfb, 0x9c, 0xe7, 0x7e, 0x19, 0x96, 0xc5, 0x8a, + 0x90, 0x11, 0x64, 0x05, 0x65, 0x84, 0xd3, 0xb2, 0x24, 0xa1, 0x46, 0xa6, 0xb3, 0x99, 0xd1, 0x9e, + 0xa7, 0x5b, 0x8a, 0xac, 0xc9, 0x74, 0x4c, 0xac, 0x08, 0x69, 0xdd, 0x9a, 0x26, 0xd6, 0x74, 0x67, + 0x93, 0x99, 0xaf, 0xc9, 0x35, 0x19, 0xdb, 0x33, 0xfa, 0x97, 0x01, 0x65, 0xd8, 0x81, 0xa3, 0x86, + 0x88, 0x24, 0x4d, 0xf7, 0x63, 0x7c, 0x11, 0xc0, 0xff, 0x9d, 0x22, 0x99, 0x6e, 0x31, 0x84, 0xfb, + 0x89, 0x02, 0x7a, 0x5f, 0xad, 0xe5, 0x8c, 0xce, 0xc3, 0x16, 0x92, 0xf6, 0x24, 0x51, 0xa3, 0x3f, + 0x81, 0x70, 0x4b, 0x56, 0xb4, 0x92, 0x58, 0x8d, 0x53, 0x29, 0x6a, 0x6d, 0x2a, 0x4b, 0xf7, 0x7b, + 0xec, 0x4c, 0xb7, 0xdc, 0x6c, 0xec, 0x70, 0xc4, 0xc0, 0xf1, 0x21, 0xfd, 0x6b, 0xaf, 0x4a, 0x7f, + 0x06, 0x61, 0xe2, 0x34, 0xee, 0x4f, 0x51, 0x6b, 0x91, 0xad, 0xe5, 0xb4, 0xc3, 0x24, 0xd2, 0x24, + 0x46, 0x36, 0xf8, 0xb6, 0xc7, 0xfa, 0x78, 0x73, 0x08, 0xbd, 0x08, 0x21, 0x55, 0xac, 0x49, 0x48, + 0x89, 0x07, 0xf4, 0x48, 0x3c, 0x69, 0xed, 0x4c, 0xbe, 0x78, 0xcd, 0xfa, 0xfe, 0x79, 0xcd, 0xfa, + 0xb8, 0x06, 0x30, 0xe3, 0x29, 0xf2, 0x48, 0x6d, 0xc9, 0x92, 0x8a, 0xe8, 0x87, 0x00, 0xc4, 0xd5, + 0x20, 0xdb, 0x85, 0x7e, 0x8f, 0x9d, 0x33, 0xb2, 0x1d, 0xd8, 0x38, 0x7e, 0x8a, 0x34, 0xf6, 0xaa, + 0x74, 0x1c, 0xc2, 0x1d, 0xa4, 0xa8, 0xa2, 0x2c, 0xe1, 0x9c, 0xa7, 0x78, 0xb3, 0xc9, 0xfd, 0x1e, + 0x80, 0xb9, 0xe1, 0x70, 0x45, 0xa5, 0x7b, 0x3d, 0x42, 0x0e, 0x20, 0xd6, 0x52, 0x50, 0x47, 0x94, + 0xdb, 0x6a, 0xc9, 0x96, 0x1b, 0x0e, 0x94, 0x4d, 0xf6, 0x7b, 0x2c, 0x43, 0x06, 0x8e, 0x83, 0x38, + 0x7e, 0xce, 0xec, 0xcd, 0x59, 0xc9, 0xda, 0x08, 0x0e, 0x5c, 0x9f, 0x60, 0x1e, 0xe6, 0x05, 0xb9, + 0x2d, 0x69, 0x48, 0x69, 0x95, 0x15, 0xad, 0x5b, 0x32, 0xe7, 0x1d, 0xc4, 0xe9, 0xb0, 0xfd, 0x1e, + 0x9b, 0x20, 0x54, 0x39, 0xa0, 0x38, 0x3e, 0x66, 0xef, 0xfe, 0xc6, 0xe8, 0xd5, 0x49, 0x6f, 0x29, + 0xb2, 0xfc, 0xb4, 0x24, 0x4a, 0xa2, 0x16, 0x9f, 0x48, 0x51, 0x6b, 0xd3, 0x76, 0xd2, 0x07, 0x36, + 0x8e, 0x9f, 0xc2, 0x0d, 0xac, 0xaa, 0x13, 0x98, 0x36, 0x2c, 0xa7, 0x48, 0xac, 0x9d, 0x6a, 0xf1, + 0x10, 0x9e, 0x0c, 0x63, 0x9b, 0x8c, 0xa1, 0xde, 0xce, 0x66, 0xfa, 0x4b, 0x8c, 0xc8, 0x26, 0xf4, + 0xa9, 0xf4, 0x7b, 0x6c, 0xcc, 0xee, 0xd7, 0x18, 0xcd, 0xf1, 0x11, 0xdc, 0x34, 0x90, 0x36, 0x19, + 0x85, 0x5d, 0x64, 0xf4, 0x08, 0x96, 0xc6, 0xea, 0x6a, 0xa9, 0xc8, 0xa6, 0x07, 0x6a, 0x58, 0x0f, + 0x7f, 0x8c, 0xe9, 0x61, 0x57, 0xa8, 0x5f, 0x4f, 0x0f, 0xc3, 0x12, 0xf5, 0x7b, 0x94, 0xe8, 0x09, + 0xdc, 0x1b, 0xaa, 0x88, 0xcd, 0x05, 0x5e, 0x29, 0x59, 0xae, 0xdf, 0x63, 0x93, 0x0e, 0xa5, 0xb3, + 0xfb, 0x5b, 0xb0, 0x5b, 0x06, 0x8a, 0xba, 0x0d, 0x4d, 0x6c, 0x82, 0x51, 0xea, 0x92, 0xa6, 0x74, + 0x89, 0x24, 0xe6, 0xfb, 0x3d, 0x36, 0x6a, 0x2f, 0x9d, 0xa6, 0x74, 0x39, 0x7e, 0x12, 0x7f, 0xeb, + 0xab, 0xea, 0xe3, 0x0a, 0x22, 0x31, 0x2a, 0x88, 0x5d, 0xa1, 0x6e, 0x0a, 0x82, 0xfb, 0xd5, 0x0f, + 0x0b, 0xc3, 0xd6, 0x9c, 0x2c, 0x3d, 0x15, 0x95, 0xe6, 0x5d, 0x94, 0xde, 0xa2, 0xb2, 0x2c, 0xd4, + 0x71, 0xb1, 0x1d, 0xa8, 0x2c, 0x0b, 0x75, 0x93, 0x4a, 0x5d, 0x90, 0xa3, 0x54, 0x06, 0x6f, 0x85, + 0xca, 0x09, 0x17, 0x2a, 0x59, 0x58, 0x71, 0x24, 0xcb, 0xa2, 0xf3, 0x15, 0x05, 0xb1, 0x01, 0x22, + 0xd7, 0x90, 0x55, 0x74, 0xfd, 0x83, 0xe6, 0xfd, 0xc8, 0xbc, 0xfa, 0x80, 0x59, 0x81, 0x84, 0x43, + 0x6e, 0x56, 0xee, 0x6f, 0xfc, 0xb0, 0x38, 0x62, 0xbf, 0x43, 0x2d, 0x0c, 0x6f, 0xb5, 0x81, 0xf7, + 0xdc, 0x6a, 0xef, 0x56, 0x0e, 0x29, 0x48, 0x3a, 0x13, 0x66, 0x71, 0xfa, 0xd2, 0x0f, 0xff, 0xdb, + 0x57, 0x6b, 0x3c, 0x12, 0x3a, 0x47, 0x65, 0xa1, 0x8e, 0x34, 0xfa, 0x31, 0x84, 0x5a, 0xf8, 0x0b, + 0x33, 0x19, 0xd9, 0x4a, 0x38, 0x9e, 0x71, 0x06, 0x98, 0x1c, 0x71, 0x64, 0x00, 0xfd, 0x04, 0xa2, + 0x46, 0xba, 0x82, 0xdc, 0x6c, 0x8a, 0x5a, 0x13, 0x49, 0x1a, 0xa6, 0x77, 0x3a, 0x9b, 0xe8, 0xf7, + 0xd8, 0x7b, 0xf6, 0x09, 0x0d, 0x10, 0x1c, 0x3f, 0x8b, 0xbb, 0x72, 0x56, 0xcf, 0x18, 0x69, 0x81, + 0x5b, 0x21, 0x2d, 0xe8, 0x42, 0xda, 0x0f, 0x78, 0xc3, 0x19, 0x30, 0x62, 0x9d, 0x4d, 0x9f, 0x43, + 0x48, 0x41, 0x6a, 0xbb, 0x61, 0x30, 0x33, 0xb3, 0xb5, 0xea, 0xc8, 0x8c, 0x09, 0xe7, 0x31, 0xb4, + 0xd8, 0x6d, 0x21, 0x9e, 0x0c, 0xdb, 0x09, 0xea, 0x31, 0xb8, 0xbf, 0xfc, 0x00, 0xfb, 0x6a, 0xad, + 0x28, 0x36, 0x91, 0xdc, 0xbe, 0x19, 0xbe, 0xdb, 0x92, 0x82, 0x04, 0x24, 0x76, 0x50, 0xd5, 0x8d, + 0xef, 0x01, 0xc2, 0xe4, 0xfb, 0xd8, 0xea, 0xb9, 0x55, 0xbe, 0xbf, 0x02, 0x5a, 0x42, 0xcf, 0xb5, + 0x92, 0x8a, 0x7e, 0x6c, 0x23, 0x49, 0x40, 0x25, 0x05, 0x09, 0x1d, 0xcc, 0x7d, 0x30, 0xbb, 0xd2, + 0xef, 0xb1, 0x4b, 0x86, 0x87, 0x71, 0x0c, 0xc7, 0x47, 0xf5, 0xce, 0x02, 0xe9, 0xd3, 0xeb, 0xe1, + 0x41, 0xf1, 0xdf, 0xe1, 0x6b, 0x34, 0xe1, 0xf6, 0xa6, 0x2b, 0xf7, 0xca, 0xb8, 0x82, 0x10, 0xef, + 0x87, 0x12, 0x5e, 0x51, 0xff, 0x85, 0x02, 0x7e, 0x0a, 0x11, 0xb2, 0xac, 0xf4, 0x8c, 0xc8, 0xe6, + 0xb4, 0xd8, 0xef, 0xb1, 0xf4, 0xd0, 0x9a, 0xd3, 0x8d, 0x1c, 0x6f, 0x6c, 0x63, 0x46, 0xee, 0xb7, + 0xb9, 0x3d, 0x39, 0x57, 0x7e, 0xe2, 0x43, 0x2b, 0x1f, 0x72, 0xa9, 0x7c, 0x05, 0xdf, 0x22, 0x86, + 0x6b, 0x73, 0xd3, 0x02, 0xf8, 0xcd, 0x8f, 0xe5, 0xb5, 0x2b, 0xd4, 0x25, 0xf9, 0x59, 0x03, 0x55, + 0x6b, 0x08, 0xef, 0x57, 0x1f, 0xa0, 0x80, 0x35, 0x98, 0x2d, 0x0f, 0x7b, 0x33, 0x04, 0xc0, 0x8f, + 0x76, 0x0f, 0x6a, 0xac, 0x0f, 0xac, 0xba, 0xd5, 0x18, 0x1b, 0xcd, 0x1a, 0xef, 0xea, 0x8d, 0x8f, + 0x7c, 0x04, 0x09, 0xf8, 0xd1, 0x38, 0xc2, 0xd8, 0x0d, 0xd7, 0x65, 0xfd, 0x17, 0x0a, 0xe8, 0x71, + 0x10, 0xfd, 0x08, 0x52, 0x7c, 0xbe, 0x70, 0x74, 0x78, 0x50, 0xc8, 0x97, 0xf8, 0x7c, 0xe1, 0xf8, + 0xeb, 0x62, 0xa9, 0xf8, 0xed, 0x51, 0xbe, 0x74, 0x7c, 0x50, 0x38, 0xca, 0xe7, 0xf6, 0x9e, 0xec, + 0xe5, 0xbf, 0x88, 0xfa, 0x98, 0xd9, 0xb3, 0xf3, 0x54, 0xc4, 0xd6, 0x45, 0xaf, 0xc2, 0x92, 0xe3, + 0xb0, 0x83, 0xc3, 0xc3, 0xa3, 0x28, 0xc5, 0x4c, 0x9e, 0x9d, 0xa7, 0x82, 0xfa, 0x37, 0xbd, 0x01, + 0xcb, 0x8e, 0xc0, 0xc2, 0x71, 0x2e, 0x97, 0x2f, 0x14, 0xa2, 0x7e, 0x26, 0x72, 0x76, 0x9e, 0x0a, + 0x93, 0x26, 0x13, 0x7c, 0xf1, 0x73, 0xd2, 0xb7, 0xf5, 0x66, 0x12, 0x02, 0xfb, 0x6a, 0x8d, 0xae, + 0xc3, 0xec, 0xe8, 0x6b, 0xdf, 0x79, 0xf6, 0xe3, 0x6f, 0x6e, 0x26, 0xe3, 0x11, 0x68, 0xf1, 0x7c, + 0x0a, 0x33, 0x23, 0x0f, 0xe9, 0xfb, 0x1e, 0x5c, 0x14, 0x95, 0x2e, 0x93, 0xf6, 0x86, 0x73, 0x89, + 0xa4, 0xdf, 0x88, 0xbd, 0x44, 0xda, 0x15, 0xea, 0x9e, 0x22, 0xd9, 0x5e, 0x06, 0xb4, 0x06, 0xb4, + 0xc3, 0xab, 0x60, 0xdd, 0x83, 0x17, 0x82, 0x65, 0xb6, 0xbc, 0x63, 0xad, 0xa8, 0x12, 0x44, 0xc7, + 0x2e, 0xcf, 0x6b, 0x57, 0xf8, 0xb1, 0x90, 0xcc, 0x03, 0xaf, 0x48, 0x2b, 0xde, 0x33, 0x88, 0x39, + 0x5e, 0x78, 0xbd, 0x38, 0x32, 0xe7, 0xb9, 0x7d, 0x0d, 0xb0, 0x15, 0xf8, 0x7b, 0x00, 0xdb, 0xad, + 0x90, 0x73, 0x73, 0x31, 0xc0, 0x30, 0xeb, 0x57, 0x63, 0x2c, 0xef, 0x05, 0x08, 0x9b, 0x17, 0x20, + 0xd6, 0x6d, 0x18, 0x01, 0x30, 0xab, 0x57, 0x00, 0xec, 0xda, 0x1b, 0x39, 0x9b, 0xef, 0x5f, 0x31, + 0x94, 0xe0, 0xdc, 0xb5, 0xe7, 0x72, 0x9e, 0xd4, 0x61, 0x76, 0xf4, 0x10, 0x70, 0xcd, 0x72, 0x04, + 0xe8, 0xbe, 0x78, 0x5d, 0x36, 0xc9, 0x6c, 0xe1, 0xed, 0x45, 0x92, 0x7a, 0x77, 0x91, 0xa4, 0xfe, + 0xbe, 0x48, 0x52, 0x2f, 0x2f, 0x93, 0xbe, 0x77, 0x97, 0x49, 0xdf, 0x9f, 0x97, 0x49, 0xdf, 0xc9, + 0xe3, 0x9a, 0xa8, 0x9d, 0xb6, 0x2b, 0x69, 0x41, 0x6e, 0x66, 0x04, 0x59, 0x6d, 0xca, 0x6a, 0x46, + 0xac, 0x08, 0x1b, 0x35, 0x39, 0xd3, 0xd9, 0xce, 0x34, 0xe5, 0x6a, 0xbb, 0x81, 0x54, 0xe3, 0xc7, + 0xe3, 0x83, 0x87, 0x1b, 0xe6, 0xbf, 0x47, 0xad, 0xdb, 0x42, 0x6a, 0x25, 0x84, 0xff, 0x3b, 0x6e, + 0xff, 0x1b, 0x00, 0x00, 0xff, 0xff, 0x51, 0x2e, 0xf7, 0xe5, 0x06, 0x15, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1478,6 +1495,13 @@ func (m *MsgChannelOpenInitResponse) MarshalToSizedBuffer(dAtA []byte) (int, err _ = 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) @@ -1586,6 +1610,13 @@ func (m *MsgChannelOpenTryResponse) MarshalToSizedBuffer(dAtA []byte) (int, erro _ = 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] = 0xa + } return len(dAtA) - i, nil } @@ -2326,6 +2357,10 @@ func (m *MsgChannelOpenInitResponse) Size() (n int) { if l > 0 { n += 1 + l + sovTx(uint64(l)) } + l = len(m.Version) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } return n } @@ -2368,6 +2403,10 @@ func (m *MsgChannelOpenTryResponse) Size() (n int) { } var l int _ = l + l = len(m.Version) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } return n } @@ -2877,6 +2916,38 @@ func (m *MsgChannelOpenInitResponse) Unmarshal(dAtA []byte) error { } 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:]) @@ -3205,6 +3276,38 @@ func (m *MsgChannelOpenTryResponse) Unmarshal(dAtA []byte) error { 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 default: iNdEx = preIndex skippy, err := skipTx(dAtA[iNdEx:]) diff --git a/modules/core/23-commitment/types/commitment_test.go b/modules/core/23-commitment/types/commitment_test.go index d1a32b9cae8..305fd2a89b4 100644 --- a/modules/core/23-commitment/types/commitment_test.go +++ b/modules/core/23-commitment/types/commitment_test.go @@ -7,6 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/store/rootmulti" storetypes "github.com/cosmos/cosmos-sdk/store/types" "github.com/stretchr/testify/suite" + "github.com/tendermint/tendermint/libs/log" dbm "github.com/tendermint/tm-db" ) @@ -20,7 +21,7 @@ type MerkleTestSuite struct { func (suite *MerkleTestSuite) SetupTest() { db := dbm.NewMemDB() - suite.store = rootmulti.NewStore(db) + suite.store = rootmulti.NewStore(db, log.NewNopLogger()) suite.storeKey = storetypes.NewKVStoreKey("iavlStoreKey") diff --git a/modules/core/23-commitment/types/merkle.go b/modules/core/23-commitment/types/merkle.go index 17f1487d982..0106495a1bb 100644 --- a/modules/core/23-commitment/types/merkle.go +++ b/modules/core/23-commitment/types/merkle.go @@ -271,8 +271,10 @@ func verifyChainedMembershipProof(root []byte, specs []*ics23.ProofSpec, proofs // blankMerkleProof and blankProofOps will be used to compare against their zero values, // and are declared as globals to avoid having to unnecessarily re-allocate on every comparison. -var blankMerkleProof = &MerkleProof{} -var blankProofOps = &tmcrypto.ProofOps{} +var ( + blankMerkleProof = &MerkleProof{} + blankProofOps = &tmcrypto.ProofOps{} +) // Empty returns true if the root is empty func (proof *MerkleProof) Empty() bool { diff --git a/modules/core/23-commitment/types/merkle_test.go b/modules/core/23-commitment/types/merkle_test.go index 28b8b3cd0dd..68a96c9b0c3 100644 --- a/modules/core/23-commitment/types/merkle_test.go +++ b/modules/core/23-commitment/types/merkle_test.go @@ -72,7 +72,6 @@ func (suite *MerkleTestSuite) TestVerifyMembership() { } }) } - } func (suite *MerkleTestSuite) TestVerifyNonMembership() { @@ -136,7 +135,6 @@ func (suite *MerkleTestSuite) TestVerifyNonMembership() { } }) } - } func TestApplyPrefix(t *testing.T) { diff --git a/modules/core/24-host/doc.go b/modules/core/24-host/doc.go index 0d73c4e7efe..4e5eaa556be 100644 --- a/modules/core/24-host/doc.go +++ b/modules/core/24-host/doc.go @@ -4,6 +4,5 @@ The storage path supported are defined in [ICS24](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/keeper/grpc_query.go b/modules/core/keeper/grpc_query.go index 2fb171a9c02..138a4e7aa01 100644 --- a/modules/core/keeper/grpc_query.go +++ b/modules/core/keeper/grpc_query.go @@ -28,6 +28,11 @@ func (q Keeper) ConsensusStates(c context.Context, req *clienttypes.QueryConsens return q.ClientKeeper.ConsensusStates(c, req) } +// ConsensusStateHeights implements the IBC QueryServer interface +func (q Keeper) ConsensusStateHeights(c context.Context, req *clienttypes.QueryConsensusStateHeightsRequest) (*clienttypes.QueryConsensusStateHeightsResponse, error) { + return q.ClientKeeper.ConsensusStateHeights(c, req) +} + // ClientStatus implements the IBC QueryServer interface func (q Keeper) ClientStatus(c context.Context, req *clienttypes.QueryClientStatusRequest) (*clienttypes.QueryClientStatusResponse, error) { return q.ClientKeeper.ClientStatus(c, req) diff --git a/modules/core/keeper/keeper_test.go b/modules/core/keeper/keeper_test.go index 47691a5ab6f..912f972d6a4 100644 --- a/modules/core/keeper/keeper_test.go +++ b/modules/core/keeper/keeper_test.go @@ -86,20 +86,17 @@ func (suite *KeeperTestSuite) TestNewKeeper() { emptyStakingKeeper := stakingkeeper.Keeper{} stakingKeeper = emptyStakingKeeper - }, false}, {"failure: empty mock staking keeper", func() { // use a different implementation of clienttypes.StakingKeeper emptyMockStakingKeeper := MockStakingKeeper{} stakingKeeper = emptyMockStakingKeeper - }, false}, {"failure: empty upgrade keeper", func() { emptyUpgradeKeeper := upgradekeeper.Keeper{} upgradeKeeper = emptyUpgradeKeeper - }, false}, {"failure: empty scoped keeper", func() { emptyScopedKeeper := capabilitykeeper.ScopedKeeper{} @@ -119,7 +116,6 @@ func (suite *KeeperTestSuite) TestNewKeeper() { suite.SetupTest() suite.Run(tc.name, func() { - stakingKeeper = suite.chainA.GetSimApp().StakingKeeper upgradeKeeper = suite.chainA.GetSimApp().UpgradeKeeper scopedKeeper = suite.chainA.GetSimApp().ScopedIBCKeeper @@ -135,7 +131,6 @@ func (suite *KeeperTestSuite) TestNewKeeper() { newIBCKeeper, ) } - }) } } diff --git a/modules/core/keeper/msg_server.go b/modules/core/keeper/msg_server.go index be18391c698..9bc54af1188 100644 --- a/modules/core/keeper/msg_server.go +++ b/modules/core/keeper/msg_server.go @@ -16,9 +16,11 @@ import ( coretypes "github.com/cosmos/ibc-go/v3/modules/core/types" ) -var _ clienttypes.MsgServer = Keeper{} -var _ connectiontypes.MsgServer = Keeper{} -var _ channeltypes.MsgServer = Keeper{} +var ( + _ clienttypes.MsgServer = Keeper{} + _ connectiontypes.MsgServer = Keeper{} + _ channeltypes.MsgServer = Keeper{} +) // CreateClient defines a rpc handler method for MsgCreateClient. func (k Keeper) CreateClient(goCtx context.Context, msg *clienttypes.MsgCreateClient) (*clienttypes.MsgCreateClientResponse, error) { @@ -194,6 +196,7 @@ func (k Keeper) ChannelOpenInit(goCtx context.Context, msg *channeltypes.MsgChan return &channeltypes.MsgChannelOpenInitResponse{ ChannelId: channelID, + Version: msg.Channel.Version, }, nil } @@ -232,7 +235,9 @@ func (k Keeper) ChannelOpenTry(goCtx context.Context, msg *channeltypes.MsgChann // Write channel into state k.ChannelKeeper.WriteOpenTryChannel(ctx, msg.PortId, channelID, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.Channel.Counterparty, version) - return &channeltypes.MsgChannelOpenTryResponse{}, nil + return &channeltypes.MsgChannelOpenTryResponse{ + Version: version, + }, nil } // ChannelOpenAck defines a rpc handler method for MsgChannelOpenAck. diff --git a/modules/core/keeper/msg_server_test.go b/modules/core/keeper/msg_server_test.go index 7e01241be81..58197f69ce8 100644 --- a/modules/core/keeper/msg_server_test.go +++ b/modules/core/keeper/msg_server_test.go @@ -415,7 +415,6 @@ func (suite *KeeperTestSuite) TestHandleTimeoutPacket() { path.EndpointA.UpdateClient() packetKey = host.NextSequenceRecvKey(packet.GetDestPort(), packet.GetDestChannel()) - }, true}, {"channel does not exist", func() { // any non-nil value of packet is valid @@ -473,7 +472,7 @@ func (suite *KeeperTestSuite) TestHandleTimeoutPacket() { // 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. +// 'TimeoutExecuted' can be found in the 04-channel/keeper/timeout_test.go. func (suite *KeeperTestSuite) TestHandleTimeoutOnClosePacket() { var ( packet channeltypes.Packet @@ -645,7 +644,6 @@ func (suite *KeeperTestSuite) TestUpgradeClient() { { name: "successful upgrade", setup: func() { - upgradedClient = ibctmtypes.NewClientState(newChainId, ibctmtypes.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod+ibctesting.TrustingPeriod, ibctesting.MaxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath, false, false) // Call ZeroCustomFields on upgraded clients to clear any client-chosen parameters in test-case upgradedClient upgradedClient = upgradedClient.ZeroCustomFields() @@ -686,7 +684,6 @@ func (suite *KeeperTestSuite) TestUpgradeClient() { { name: "VerifyUpgrade fails", setup: func() { - upgradedClient = ibctmtypes.NewClientState(newChainId, ibctmtypes.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod+ibctesting.TrustingPeriod, ibctesting.MaxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath, false, false) // Call ZeroCustomFields on upgraded clients to clear any client-chosen parameters in test-case upgradedClient upgradedClient = upgradedClient.ZeroCustomFields() diff --git a/modules/light-clients/01-dymint/types/client_state.go b/modules/light-clients/01-dymint/types/client_state.go index 182bfd6d630..69aff098fd2 100644 --- a/modules/light-clients/01-dymint/types/client_state.go +++ b/modules/light-clients/01-dymint/types/client_state.go @@ -25,18 +25,16 @@ func NewClientState( chainID string, trustingPeriod, maxClockDrift time.Duration, latestHeight clienttypes.Height, specs []*ics23.ProofSpec, - upgradePath []string, allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour bool, + upgradePath []string, ) *ClientState { return &ClientState{ - ChainId: chainID, - TrustingPeriod: trustingPeriod, - MaxClockDrift: maxClockDrift, - LatestHeight: latestHeight, - FrozenHeight: clienttypes.ZeroHeight(), - ProofSpecs: specs, - UpgradePath: upgradePath, - AllowUpdateAfterExpiry: allowUpdateAfterExpiry, - AllowUpdateAfterMisbehaviour: allowUpdateAfterMisbehaviour, + ChainId: chainID, + TrustingPeriod: trustingPeriod, + MaxClockDrift: maxClockDrift, + LatestHeight: latestHeight, + FrozenHeight: clienttypes.ZeroHeight(), + ProofSpecs: specs, + UpgradePath: upgradePath, } } diff --git a/modules/light-clients/01-dymint/types/client_state_test.go b/modules/light-clients/01-dymint/types/client_state_test.go index e5c2e8588b6..5c360f18bf4 100644 --- a/modules/light-clients/01-dymint/types/client_state_test.go +++ b/modules/light-clients/01-dymint/types/client_state_test.go @@ -91,17 +91,17 @@ func (suite *DymintTestSuite) TestValidate() { }{ { name: "valid client", - clientState: types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + clientState: types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), expPass: true, }, { name: "valid client with nil upgrade path", - clientState: types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), nil, false, false), + clientState: types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), nil), expPass: true, }, { name: "invalid chainID", - clientState: types.NewClientState(" ", trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + clientState: types.NewClientState(" ", trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), expPass: false, }, { @@ -109,7 +109,7 @@ func (suite *DymintTestSuite) TestValidate() { // Do not only fix the test, fix the code! // https://github.com/cosmos/ibc-go/issues/177 name: "valid chainID - chainID validation failed for chainID of length 50! ", - clientState: types.NewClientState(fiftyCharChainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + clientState: types.NewClientState(fiftyCharChainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), expPass: true, }, { @@ -117,37 +117,37 @@ func (suite *DymintTestSuite) TestValidate() { // Do not only fix the test, fix the code! // https://github.com/cosmos/ibc-go/issues/177 name: "invalid chainID - chainID validation did not fail for chainID of length 51! ", - clientState: types.NewClientState(fiftyOneCharChainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + clientState: types.NewClientState(fiftyOneCharChainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), expPass: false, }, { name: "invalid trusting period", - clientState: types.NewClientState(chainID, 0, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + clientState: types.NewClientState(chainID, 0, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), expPass: false, }, { name: "invalid max clock drift", - clientState: types.NewClientState(chainID, trustingPeriod, 0, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + clientState: types.NewClientState(chainID, trustingPeriod, 0, height, commitmenttypes.GetSDKSpecs(), upgradePath), expPass: false, }, { name: "invalid revision number", - clientState: types.NewClientState(chainID, trustingPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + clientState: types.NewClientState(chainID, trustingPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), upgradePath), expPass: false, }, { name: "invalid revision height", - clientState: types.NewClientState(chainID, trustingPeriod, maxClockDrift, clienttypes.ZeroHeight(), commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + clientState: types.NewClientState(chainID, trustingPeriod, maxClockDrift, clienttypes.ZeroHeight(), commitmenttypes.GetSDKSpecs(), upgradePath), expPass: false, }, { name: "proof specs is nil", - clientState: types.NewClientState(chainID, ubdPeriod, maxClockDrift, height, nil, upgradePath, false, false), + clientState: types.NewClientState(chainID, ubdPeriod, maxClockDrift, height, nil, upgradePath), expPass: false, }, { name: "proof specs contains nil", - clientState: types.NewClientState(chainID, ubdPeriod, maxClockDrift, height, []*ics23.ProofSpec{ics23.TendermintSpec, nil}, upgradePath, false, false), + clientState: types.NewClientState(chainID, ubdPeriod, maxClockDrift, height, []*ics23.ProofSpec{ics23.TendermintSpec, nil}, upgradePath), expPass: false, }, } @@ -231,7 +231,7 @@ func (suite *DymintTestSuite) TestVerifyClientConsensusState() { // }, { name: "ApplyPrefix failed", - clientState: types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + clientState: types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), consensusState: &types.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), }, @@ -240,7 +240,7 @@ func (suite *DymintTestSuite) TestVerifyClientConsensusState() { }, { name: "latest client height < height", - clientState: types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + clientState: types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), consensusState: &types.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), }, @@ -249,7 +249,7 @@ func (suite *DymintTestSuite) TestVerifyClientConsensusState() { }, { name: "proof verification failed", - clientState: types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + clientState: types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), consensusState: &types.ConsensusState{ Root: commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), NextValidatorsHash: suite.valsHash, diff --git a/modules/light-clients/01-dymint/types/consensus_state_test.go b/modules/light-clients/01-dymint/types/consensus_state_test.go index 8e32c18998c..9789f55f761 100644 --- a/modules/light-clients/01-dymint/types/consensus_state_test.go +++ b/modules/light-clients/01-dymint/types/consensus_state_test.go @@ -14,41 +14,52 @@ func (suite *DymintTestSuite) TestConsensusStateValidateBasic() { consensusState *types.ConsensusState expectPass bool }{ - {"success", + { + "success", &types.ConsensusState{ Timestamp: suite.now, Root: commitmenttypes.NewMerkleRoot([]byte("app_hash")), NextValidatorsHash: suite.valsHash, }, - true}, - {"success with sentinel", + true, + }, + { + "success with sentinel", &types.ConsensusState{ Timestamp: suite.now, Root: commitmenttypes.NewMerkleRoot([]byte(types.SentinelRoot)), NextValidatorsHash: suite.valsHash, }, - true}, - {"root is nil", + true, + }, + { + "root is nil", &types.ConsensusState{ Timestamp: suite.now, Root: commitmenttypes.MerkleRoot{}, NextValidatorsHash: suite.valsHash, }, - false}, - {"root is empty", + false, + }, + { + "root is empty", &types.ConsensusState{ Timestamp: suite.now, Root: commitmenttypes.MerkleRoot{}, NextValidatorsHash: suite.valsHash, }, - false}, - {"timestamp is zero", + false, + }, + + { + "timestamp is zero", &types.ConsensusState{ Timestamp: time.Time{}, Root: commitmenttypes.NewMerkleRoot([]byte("app_hash")), NextValidatorsHash: suite.valsHash, }, - false}, + false, + }, } for i, tc := range testCases { diff --git a/modules/light-clients/01-dymint/types/dymint.pb.go b/modules/light-clients/01-dymint/types/dymint.pb.go index 8b55960e90e..31f0b4ab57b 100644 --- a/modules/light-clients/01-dymint/types/dymint.pb.go +++ b/modules/light-clients/01-dymint/types/dymint.pb.go @@ -59,12 +59,6 @@ type ClientState struct { // 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" yaml:"upgrade_path"` - // This flag, when set to true, will allow governance to recover a client - // which has expired - AllowUpdateAfterExpiry bool `protobuf:"varint,10,opt,name=allow_update_after_expiry,json=allowUpdateAfterExpiry,proto3" json:"allow_update_after_expiry,omitempty" yaml:"allow_update_after_expiry"` - // This flag, when set to true, will allow governance to unfreeze a client - // whose chain has experienced a misbehaviour event - AllowUpdateAfterMisbehaviour bool `protobuf:"varint,11,opt,name=allow_update_after_misbehaviour,json=allowUpdateAfterMisbehaviour,proto3" json:"allow_update_after_misbehaviour,omitempty" yaml:"allow_update_after_misbehaviour"` } func (m *ClientState) Reset() { *m = ClientState{} } @@ -312,11 +306,11 @@ func (m *Fraction) GetDenominator() uint64 { } func init() { - proto.RegisterType((*ClientState)(nil), "ibc.lightclients.dymint.v1.ClientState") - proto.RegisterType((*ConsensusState)(nil), "ibc.lightclients.dymint.v1.ConsensusState") - proto.RegisterType((*Misbehaviour)(nil), "ibc.lightclients.dymint.v1.Misbehaviour") - proto.RegisterType((*Header)(nil), "ibc.lightclients.dymint.v1.Header") - proto.RegisterType((*Fraction)(nil), "ibc.lightclients.dymint.v1.Fraction") + proto.RegisterType((*ClientState)(nil), "ibc.lightclients.dymint.ClientState") + proto.RegisterType((*ConsensusState)(nil), "ibc.lightclients.dymint.ConsensusState") + proto.RegisterType((*Misbehaviour)(nil), "ibc.lightclients.dymint.Misbehaviour") + proto.RegisterType((*Header)(nil), "ibc.lightclients.dymint.Header") + proto.RegisterType((*Fraction)(nil), "ibc.lightclients.dymint.Fraction") } func init() { @@ -324,75 +318,70 @@ func init() { } var fileDescriptor_cef6cb256dd4d990 = []byte{ - // 1083 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x56, 0xcf, 0x6f, 0xe3, 0xc4, - 0x17, 0x6f, 0xda, 0x7e, 0xb7, 0xc9, 0x24, 0xdd, 0xee, 0xd7, 0x94, 0x6e, 0x5a, 0xba, 0x71, 0x34, - 0x54, 0x50, 0x21, 0xd5, 0x26, 0x29, 0xa7, 0x8a, 0xcb, 0xba, 0x0b, 0x6a, 0x11, 0x2b, 0x55, 0x2e, - 0x3f, 0x24, 0xd0, 0xca, 0x4c, 0xec, 0x49, 0x3c, 0x5a, 0xdb, 0x63, 0x3c, 0xe3, 0xd0, 0x72, 0x47, - 0x82, 0xdb, 0x1e, 0x11, 0x27, 0x0e, 0xfc, 0x31, 0x7b, 0xec, 0x91, 0x93, 0x41, 0x2d, 0x7f, 0x41, - 0x8e, 0x9c, 0x90, 0x67, 0xc6, 0x3f, 0xd2, 0x6d, 0x55, 0xf5, 0xd2, 0xce, 0x7b, 0xef, 0xf3, 0x3e, - 0x9f, 0xcc, 0x9b, 0x37, 0x6f, 0x0c, 0x76, 0xc8, 0xc8, 0x35, 0x03, 0x32, 0xf1, 0xb9, 0x1b, 0x10, - 0x1c, 0x71, 0x66, 0x7a, 0xe7, 0x21, 0x89, 0xb8, 0xfa, 0x67, 0xc4, 0x09, 0xe5, 0x54, 0xdb, 0x22, - 0x23, 0xd7, 0xa8, 0xa3, 0x0c, 0x15, 0x9e, 0x0e, 0xb6, 0xfa, 0x1c, 0x47, 0x1e, 0x4e, 0x44, 0x12, - 0x3f, 0x8f, 0x31, 0x33, 0xa7, 0x28, 0x20, 0x1e, 0xe2, 0x34, 0x91, 0xd9, 0x5b, 0xdb, 0x6f, 0x20, - 0xc4, 0x5f, 0x15, 0xed, 0xc4, 0x09, 0xa5, 0xe3, 0xc2, 0xea, 0x4d, 0x28, 0x9d, 0x04, 0xd8, 0x14, - 0xd6, 0x28, 0x1d, 0x9b, 0x5e, 0x9a, 0x20, 0x4e, 0x68, 0xa4, 0xe2, 0xfa, 0xf5, 0x38, 0x27, 0x21, - 0x66, 0x1c, 0x85, 0x71, 0x01, 0xc8, 0x37, 0xe4, 0xd2, 0x04, 0x9b, 0xf2, 0xa7, 0x9a, 0xd3, 0x81, - 0x5a, 0x29, 0xc0, 0xfb, 0x15, 0x80, 0x86, 0x21, 0xe1, 0x61, 0x01, 0x2a, 0x2d, 0x05, 0x5c, 0x9f, - 0xd0, 0x09, 0x15, 0x4b, 0x33, 0x5f, 0x49, 0x2f, 0xfc, 0x67, 0x05, 0xb4, 0x0f, 0x05, 0xdf, 0x29, - 0x47, 0x1c, 0x6b, 0x9b, 0xa0, 0xe9, 0xfa, 0x88, 0x44, 0x0e, 0xf1, 0xba, 0x8d, 0x7e, 0x63, 0xb7, - 0x65, 0xaf, 0x08, 0xfb, 0xd8, 0xd3, 0x10, 0x68, 0xf3, 0x24, 0x65, 0xdc, 0x09, 0xf0, 0x14, 0x07, - 0xdd, 0xc5, 0x7e, 0x63, 0xb7, 0x3d, 0xdc, 0x31, 0x6e, 0xaf, 0xa5, 0xf1, 0x69, 0x82, 0xdc, 0x7c, - 0xb3, 0xd6, 0xd6, 0xeb, 0x4c, 0x5f, 0x98, 0x65, 0xba, 0x76, 0x8e, 0xc2, 0xe0, 0x00, 0xd6, 0x68, - 0xa0, 0x0d, 0x84, 0xf5, 0x79, 0x6e, 0x68, 0x63, 0xb0, 0x26, 0x2c, 0x12, 0x4d, 0x9c, 0x18, 0x27, - 0x84, 0x7a, 0xdd, 0x25, 0x21, 0xb3, 0x69, 0xc8, 0x42, 0x19, 0x45, 0xa1, 0x8c, 0x67, 0xaa, 0x90, - 0x16, 0x54, 0xdc, 0x1b, 0x35, 0xee, 0x2a, 0x1f, 0xfe, 0xfa, 0x97, 0xde, 0xb0, 0x1f, 0x16, 0xde, - 0x13, 0xe1, 0xd4, 0x08, 0x78, 0x94, 0x46, 0x23, 0x1a, 0x79, 0x35, 0xa1, 0xe5, 0xbb, 0x84, 0xde, - 0x55, 0x42, 0x8f, 0xa5, 0xd0, 0x75, 0x02, 0xa9, 0xb4, 0x56, 0xba, 0x95, 0x14, 0x06, 0x6b, 0x21, - 0x3a, 0x73, 0xdc, 0x80, 0xba, 0x2f, 0x1d, 0x2f, 0x21, 0x63, 0xde, 0xfd, 0xdf, 0x3d, 0xb7, 0x74, - 0x2d, 0x5f, 0x0a, 0xad, 0x86, 0xe8, 0xec, 0x30, 0x77, 0x3e, 0xcb, 0x7d, 0xda, 0x0b, 0xb0, 0x3a, - 0x4e, 0xe8, 0x8f, 0x38, 0x72, 0x7c, 0x9c, 0x1f, 0x46, 0xf7, 0x81, 0x10, 0xd9, 0x12, 0xc7, 0x93, - 0xb7, 0x87, 0xa1, 0xba, 0x66, 0x3a, 0x30, 0x8e, 0x04, 0xc2, 0xda, 0x56, 0x2a, 0xeb, 0x52, 0x65, - 0x2e, 0x1d, 0xda, 0x1d, 0x69, 0x4b, 0x6c, 0x4e, 0x1f, 0x20, 0x8e, 0x19, 0x2f, 0xe8, 0x57, 0xee, - 0x4b, 0x3f, 0x97, 0x0e, 0xed, 0x8e, 0xb4, 0x15, 0xfd, 0x31, 0x68, 0x8b, 0x6b, 0xe3, 0xb0, 0x18, - 0xbb, 0xac, 0xdb, 0xec, 0x2f, 0xed, 0xb6, 0x87, 0x8f, 0x0c, 0xe2, 0xb2, 0xe1, 0xbe, 0x71, 0x92, - 0x47, 0x4e, 0x63, 0xec, 0x5a, 0x1b, 0x55, 0x0b, 0xd5, 0xe0, 0xd0, 0x06, 0x71, 0x01, 0x61, 0xda, - 0x01, 0xe8, 0xa4, 0xf1, 0x24, 0x41, 0x1e, 0x76, 0x62, 0xc4, 0xfd, 0x6e, 0xab, 0xbf, 0xb4, 0xdb, - 0xb2, 0x1e, 0xcf, 0x32, 0xfd, 0x2d, 0x75, 0x6e, 0xb5, 0x28, 0xb4, 0xdb, 0xca, 0x3c, 0x41, 0xdc, - 0xd7, 0x1c, 0xb0, 0x89, 0x82, 0x80, 0xfe, 0xe0, 0xa4, 0xb1, 0x87, 0x38, 0x76, 0xd0, 0x98, 0xe3, - 0xc4, 0xc1, 0x67, 0x31, 0x49, 0xce, 0xbb, 0xa0, 0xdf, 0xd8, 0x6d, 0x5a, 0x3b, 0xb3, 0x4c, 0xef, - 0x4b, 0xa2, 0x5b, 0xa1, 0xd0, 0xde, 0x10, 0xb1, 0x2f, 0x45, 0xe8, 0x69, 0x1e, 0xf9, 0x44, 0x04, - 0xb4, 0xef, 0x81, 0x7e, 0x43, 0x56, 0x48, 0xd8, 0x08, 0xfb, 0x68, 0x4a, 0x68, 0x9a, 0x74, 0xdb, - 0x42, 0xe6, 0x83, 0x59, 0xa6, 0xbf, 0x77, 0xab, 0x4c, 0x3d, 0x01, 0xda, 0xdb, 0xd7, 0xc5, 0x9e, - 0xd7, 0xc2, 0x07, 0xcb, 0x3f, 0xff, 0xae, 0x2f, 0xc0, 0x3f, 0x16, 0xc1, 0xc3, 0x43, 0x1a, 0x31, - 0x1c, 0xb1, 0x94, 0xc9, 0x9b, 0x6e, 0x81, 0x56, 0x39, 0x6c, 0xc4, 0x55, 0xcf, 0x8f, 0xf3, 0x7a, - 0x4b, 0x7e, 0x51, 0x20, 0xac, 0x66, 0x7e, 0x9c, 0xaf, 0xf2, 0xce, 0xab, 0xd2, 0xb4, 0x8f, 0xc1, - 0x72, 0x42, 0x29, 0x57, 0xb3, 0x00, 0xd6, 0xba, 0xa1, 0x9a, 0x3e, 0xd3, 0x81, 0xf1, 0x1c, 0x27, - 0x2f, 0x03, 0x6c, 0x53, 0xca, 0xad, 0xe5, 0x9c, 0xc6, 0x16, 0x59, 0xda, 0x2f, 0x0d, 0xb0, 0x1e, - 0xe1, 0x33, 0xee, 0x94, 0x13, 0x96, 0x39, 0x3e, 0x62, 0xbe, 0xb8, 0xf3, 0x1d, 0xeb, 0xeb, 0x59, - 0xa6, 0xbf, 0x23, 0x6b, 0x70, 0x13, 0x0a, 0xfe, 0x9b, 0xe9, 0x1f, 0x4d, 0x08, 0xf7, 0xd3, 0x51, - 0x2e, 0x67, 0xd6, 0xa7, 0x72, 0xb5, 0x0c, 0xc8, 0x88, 0x99, 0xa3, 0x73, 0x8e, 0x99, 0x71, 0x84, - 0xcf, 0xac, 0x7c, 0x61, 0x6b, 0x39, 0xdd, 0x57, 0x25, 0xdb, 0x11, 0x62, 0xbe, 0x2a, 0xd3, 0x4f, - 0x8b, 0xa0, 0x53, 0xaf, 0x9e, 0x36, 0x00, 0x2d, 0xd9, 0xd8, 0xe5, 0x3c, 0xb4, 0xd6, 0x67, 0x99, - 0xfe, 0x48, 0xfe, 0xac, 0x32, 0x04, 0xed, 0xa6, 0x5c, 0x1f, 0x7b, 0xda, 0x0b, 0xd0, 0xf4, 0x31, - 0xf2, 0x70, 0xe2, 0x0c, 0xe6, 0xea, 0x72, 0xcb, 0x8c, 0x3c, 0x12, 0x58, 0xab, 0x77, 0x99, 0xe9, - 0x2b, 0x72, 0x3d, 0x98, 0x65, 0xfa, 0x9a, 0x14, 0x28, 0x88, 0xa0, 0xbd, 0x22, 0x97, 0x83, 0x1a, - 0xfd, 0x50, 0xcd, 0xc6, 0x7b, 0xd2, 0x0f, 0xdf, 0xa0, 0x1f, 0x96, 0xf4, 0x43, 0x55, 0x87, 0xdf, - 0x96, 0xc0, 0x03, 0x89, 0xd6, 0x10, 0x58, 0x65, 0x64, 0x12, 0x61, 0xcf, 0x91, 0x10, 0xd5, 0x2a, - 0x3d, 0xa3, 0x2a, 0xb2, 0x21, 0xdf, 0xbf, 0x53, 0x01, 0x53, 0x82, 0xdb, 0x17, 0x99, 0xde, 0xa8, - 0x6e, 0xff, 0x1c, 0x05, 0xb4, 0x3b, 0xac, 0x86, 0xcd, 0x87, 0x4b, 0x79, 0xb6, 0x0e, 0xc3, 0x45, - 0x3b, 0xdd, 0x20, 0x51, 0x1e, 0xda, 0x29, 0xe6, 0x56, 0xb7, 0xa2, 0x9f, 0x4b, 0x87, 0x76, 0x67, - 0x5a, 0xc3, 0x69, 0xdf, 0x01, 0x39, 0xfe, 0x85, 0xbe, 0x18, 0x5e, 0x4b, 0x77, 0x0e, 0xaf, 0x27, - 0x6a, 0x78, 0xbd, 0x5d, 0x7b, 0x54, 0xca, 0x7c, 0x68, 0xaf, 0x2a, 0x87, 0x1a, 0x5f, 0x01, 0xd0, - 0x0a, 0x44, 0xd5, 0xa4, 0xea, 0x41, 0xb9, 0x6b, 0x17, 0x4f, 0x66, 0x99, 0xbe, 0x39, 0xaf, 0x52, - 0x71, 0x40, 0xfb, 0xff, 0xca, 0x59, 0xb5, 0x2b, 0xfc, 0x0c, 0x34, 0x8b, 0x87, 0x55, 0xdb, 0x06, - 0xad, 0x28, 0x0d, 0x71, 0x92, 0x47, 0xc4, 0xc9, 0x2c, 0xdb, 0x95, 0x43, 0xeb, 0x83, 0xb6, 0x87, - 0x23, 0x1a, 0x92, 0x48, 0xc4, 0x17, 0x45, 0xbc, 0xee, 0xb2, 0xbe, 0x7d, 0x7d, 0xd9, 0x6b, 0x5c, - 0x5c, 0xf6, 0x1a, 0x7f, 0x5f, 0xf6, 0x1a, 0xaf, 0xae, 0x7a, 0x0b, 0x17, 0x57, 0xbd, 0x85, 0x3f, - 0xaf, 0x7a, 0x0b, 0xdf, 0x3c, 0xad, 0x5d, 0x2d, 0x97, 0xb2, 0x90, 0x32, 0x93, 0x8c, 0xdc, 0xbd, - 0x09, 0x35, 0xa7, 0xfb, 0x66, 0x48, 0xbd, 0x34, 0xc0, 0x4c, 0x7e, 0x69, 0xed, 0x15, 0x9f, 0x5a, - 0x1f, 0x0e, 0xf6, 0xd4, 0xd7, 0x96, 0xd8, 0xe7, 0xe8, 0x81, 0x18, 0x23, 0xfb, 0xff, 0x05, 0x00, - 0x00, 0xff, 0xff, 0x0d, 0x0a, 0x5e, 0x40, 0x95, 0x09, 0x00, 0x00, + // 1002 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x56, 0x4f, 0x6f, 0xe3, 0x44, + 0x14, 0x8f, 0x9b, 0xd0, 0x26, 0x93, 0x74, 0x5b, 0x4c, 0xd9, 0xa6, 0xa5, 0x1b, 0x07, 0x83, 0x44, + 0x2f, 0xb5, 0x49, 0xca, 0xa9, 0xe2, 0x82, 0xbb, 0x42, 0x2d, 0x62, 0xa5, 0xca, 0x45, 0x20, 0x2d, + 0x02, 0xe3, 0x3f, 0x13, 0x7b, 0xb4, 0xb6, 0xc7, 0xf2, 0x8c, 0xa3, 0x96, 0x4f, 0x00, 0x07, 0xa4, + 0x3d, 0x22, 0x4e, 0x1c, 0xf8, 0x30, 0x7b, 0xec, 0x91, 0x93, 0x41, 0xed, 0x37, 0xc8, 0x91, 0x0b, + 0x2b, 0xcf, 0x8c, 0xff, 0xa4, 0xbb, 0x55, 0xd5, 0x4b, 0x32, 0xef, 0xbd, 0xdf, 0xfb, 0xfd, 0x3c, + 0x6f, 0xe6, 0x3d, 0x1b, 0x7c, 0x8c, 0x1c, 0x57, 0x0f, 0x91, 0x1f, 0x50, 0x37, 0x44, 0x30, 0xa6, + 0x44, 0xf7, 0x2e, 0x23, 0x14, 0x53, 0xf1, 0xa7, 0x25, 0x29, 0xa6, 0x58, 0xde, 0x46, 0x8e, 0xab, + 0x35, 0x51, 0x1a, 0x0f, 0xef, 0x8e, 0x29, 0x8c, 0x3d, 0x98, 0xb2, 0x0c, 0x7a, 0x99, 0x40, 0xa2, + 0xcf, 0xed, 0x10, 0x79, 0x36, 0xc5, 0x29, 0x4f, 0xdd, 0xdd, 0x7b, 0x03, 0xc1, 0x7e, 0x45, 0x74, + 0x90, 0xa4, 0x18, 0xcf, 0x4a, 0x6b, 0xe4, 0x63, 0xec, 0x87, 0x50, 0x67, 0x96, 0x93, 0xcd, 0x74, + 0x2f, 0x4b, 0x6d, 0x8a, 0x70, 0x2c, 0xe2, 0xca, 0xed, 0x38, 0x45, 0x11, 0x24, 0xd4, 0x8e, 0x92, + 0x12, 0x50, 0xec, 0xc6, 0xc5, 0x29, 0xd4, 0xf9, 0x73, 0xea, 0xf3, 0x89, 0x58, 0x09, 0xc0, 0x27, + 0x35, 0x00, 0x47, 0x11, 0xa2, 0x51, 0x09, 0xaa, 0x2c, 0x01, 0xdc, 0xf2, 0xb1, 0x8f, 0xd9, 0x52, + 0x2f, 0x56, 0xdc, 0xab, 0xfe, 0xb6, 0x0a, 0xfa, 0xc7, 0x8c, 0xef, 0x9c, 0xda, 0x14, 0xca, 0x3b, + 0xa0, 0xeb, 0x06, 0x36, 0x8a, 0x2d, 0xe4, 0x0d, 0xa5, 0xb1, 0xb4, 0xdf, 0x33, 0xd7, 0x98, 0x7d, + 0xea, 0xc9, 0x3f, 0x82, 0x3e, 0x4d, 0x33, 0x42, 0xad, 0x10, 0xce, 0x61, 0x38, 0x5c, 0x19, 0x4b, + 0xfb, 0xfd, 0xe9, 0x87, 0xda, 0x1d, 0x85, 0xd4, 0xbe, 0x4c, 0x6d, 0xb7, 0xd8, 0xa9, 0xb1, 0xfb, + 0x2a, 0x57, 0x5a, 0x8b, 0x5c, 0x91, 0x2f, 0xed, 0x28, 0x3c, 0x52, 0x1b, 0x1c, 0xaa, 0x09, 0x98, + 0xf5, 0x75, 0x61, 0xc8, 0x33, 0xb0, 0xc1, 0x2c, 0x14, 0xfb, 0x56, 0x02, 0x53, 0x84, 0xbd, 0x61, + 0x9b, 0x69, 0xec, 0x68, 0xbc, 0x4a, 0x5a, 0x59, 0x25, 0xed, 0xa9, 0xa8, 0xa2, 0xa1, 0x0a, 0xee, + 0xc7, 0x0d, 0xee, 0x3a, 0x5f, 0xfd, 0xfd, 0x1f, 0x45, 0x32, 0x1f, 0x95, 0xde, 0x33, 0xe6, 0x94, + 0x11, 0xd8, 0xcc, 0x62, 0x07, 0xc7, 0x5e, 0x43, 0xa8, 0x73, 0x9f, 0xd0, 0x47, 0x42, 0x68, 0x9b, + 0x0b, 0xdd, 0x26, 0xe0, 0x4a, 0x1b, 0x95, 0x5b, 0x48, 0x41, 0xb0, 0x11, 0xd9, 0x17, 0x96, 0x1b, + 0x62, 0xf7, 0x85, 0xe5, 0xa5, 0x68, 0x46, 0x87, 0xef, 0x3c, 0x70, 0x4b, 0xb7, 0xf2, 0xb9, 0xd0, + 0x7a, 0x64, 0x5f, 0x1c, 0x17, 0xce, 0xa7, 0x85, 0x4f, 0xfe, 0x01, 0xac, 0xcf, 0x52, 0xfc, 0x33, + 0x8c, 0xad, 0x00, 0x16, 0x27, 0x31, 0x5c, 0x65, 0x22, 0xbb, 0xec, 0x6c, 0x8a, 0xbb, 0xa1, 0x89, + 0x2b, 0x33, 0x9f, 0x68, 0x27, 0x0c, 0x61, 0xec, 0x09, 0x95, 0x2d, 0xae, 0xb2, 0x94, 0xae, 0x9a, + 0x03, 0x6e, 0x73, 0x6c, 0x41, 0x1f, 0xda, 0x14, 0x12, 0x5a, 0xd2, 0xaf, 0x3d, 0x94, 0x7e, 0x29, + 0x5d, 0x35, 0x07, 0xdc, 0x16, 0xf4, 0xa7, 0xa0, 0xcf, 0x7a, 0xc6, 0x22, 0x09, 0x74, 0xc9, 0xb0, + 0x3b, 0x6e, 0xef, 0xf7, 0xa7, 0x9b, 0x1a, 0x72, 0xc9, 0xf4, 0x50, 0x3b, 0x2b, 0x22, 0xe7, 0x09, + 0x74, 0x8d, 0xc7, 0xf5, 0x15, 0x6a, 0xc0, 0x55, 0x13, 0x24, 0x25, 0x84, 0xc8, 0x47, 0x60, 0x90, + 0x25, 0x7e, 0x6a, 0x7b, 0xd0, 0x4a, 0x6c, 0x1a, 0x0c, 0x7b, 0xe3, 0xf6, 0x7e, 0xcf, 0xd8, 0x5e, + 0xe4, 0xca, 0x7b, 0xe2, 0xdc, 0x1a, 0x51, 0xd5, 0xec, 0x0b, 0xf3, 0xcc, 0xa6, 0xc1, 0x51, 0xe7, + 0x97, 0x3f, 0x95, 0x96, 0xfa, 0xd7, 0x0a, 0x78, 0x74, 0x8c, 0x63, 0x02, 0x63, 0x92, 0x11, 0xde, + 0x12, 0x06, 0xe8, 0x55, 0x5d, 0xc9, 0x7a, 0xa2, 0xd8, 0xfa, 0xed, 0xe3, 0xfb, 0xa6, 0x44, 0x18, + 0xdd, 0x62, 0xeb, 0x2f, 0x8b, 0x53, 0xaa, 0xd3, 0xe4, 0xcf, 0x41, 0x27, 0xc5, 0x98, 0x8a, 0xa6, + 0x51, 0x1b, 0x95, 0xab, 0xdb, 0x74, 0x3e, 0xd1, 0x9e, 0xc1, 0xf4, 0x45, 0x08, 0x4d, 0x8c, 0xa9, + 0xd1, 0x29, 0x68, 0x4c, 0x96, 0x25, 0xff, 0x2a, 0x81, 0xad, 0x18, 0x5e, 0x50, 0xab, 0x1a, 0x45, + 0xc4, 0x0a, 0x6c, 0x12, 0xb0, 0xfe, 0x18, 0x18, 0xdf, 0x2d, 0x72, 0xe5, 0x03, 0xbe, 0xbf, 0xb7, + 0xa1, 0xd4, 0xff, 0x72, 0xe5, 0x33, 0x1f, 0xd1, 0x20, 0x73, 0x0a, 0x39, 0xbd, 0x39, 0xbe, 0xea, + 0x65, 0x88, 0x1c, 0xa2, 0x3b, 0x97, 0x14, 0x12, 0xed, 0x04, 0x5e, 0x18, 0xc5, 0xc2, 0x94, 0x0b, + 0xba, 0x6f, 0x2b, 0xb6, 0x13, 0x9b, 0x94, 0x65, 0xfa, 0x5f, 0x02, 0x83, 0x67, 0x88, 0x38, 0x30, + 0xb0, 0xe7, 0x08, 0x67, 0xa9, 0x3c, 0x01, 0x3d, 0x7e, 0x09, 0xaa, 0xc1, 0x61, 0x6c, 0x2d, 0x72, + 0x65, 0x93, 0x3f, 0x56, 0x15, 0x52, 0xcd, 0x2e, 0x5f, 0x9f, 0x7a, 0xf2, 0x73, 0xd0, 0x0d, 0xa0, + 0xed, 0xc1, 0xd4, 0x9a, 0x88, 0xba, 0x28, 0x77, 0x0e, 0x93, 0x13, 0x06, 0x34, 0x46, 0xd7, 0xb9, + 0xb2, 0xc6, 0xd7, 0x93, 0x45, 0xae, 0x6c, 0x70, 0xf6, 0x92, 0x45, 0x35, 0xd7, 0xf8, 0x72, 0xd2, + 0xe0, 0x9e, 0x8a, 0x21, 0xf2, 0x10, 0xee, 0xe9, 0x1b, 0xdc, 0xd3, 0x8a, 0x7b, 0x2a, 0x2a, 0xf0, + 0x47, 0x1b, 0xac, 0x72, 0xb4, 0x6c, 0x83, 0x75, 0x82, 0xfc, 0x18, 0x7a, 0x16, 0x87, 0x88, 0x4b, + 0x32, 0xd2, 0xea, 0xf2, 0x6a, 0xfc, 0x15, 0x71, 0xce, 0x60, 0x42, 0x70, 0xef, 0x2a, 0x57, 0xa4, + 0xba, 0x47, 0x96, 0x28, 0x54, 0x73, 0x40, 0x1a, 0xd8, 0xa2, 0x05, 0xab, 0x53, 0xb5, 0x08, 0x2c, + 0x2f, 0xd2, 0x5b, 0x24, 0xaa, 0xe3, 0x3a, 0x87, 0xd4, 0x18, 0xd6, 0xf4, 0x4b, 0xe9, 0xaa, 0x39, + 0x98, 0x37, 0x70, 0xf2, 0x4f, 0x80, 0x0f, 0x49, 0xa6, 0xcf, 0x5a, 0xbc, 0x7d, 0x6f, 0x8b, 0x3f, + 0x11, 0x2d, 0xfe, 0x7e, 0x63, 0xf4, 0x56, 0xf9, 0xaa, 0xb9, 0x2e, 0x1c, 0xa2, 0xc9, 0x43, 0x20, + 0x97, 0x88, 0xfa, 0x7a, 0x8a, 0xb1, 0x7b, 0xdf, 0x2e, 0x9e, 0x2c, 0x72, 0x65, 0x67, 0x59, 0xa5, + 0xe6, 0x50, 0xcd, 0x77, 0x85, 0xb3, 0xbe, 0xa8, 0xea, 0x57, 0xa0, 0x5b, 0xbe, 0x7e, 0xe4, 0x3d, + 0xd0, 0x8b, 0xb3, 0x08, 0xa6, 0x45, 0x84, 0x9d, 0x4c, 0xc7, 0xac, 0x1d, 0xf2, 0x18, 0xf4, 0x3d, + 0x18, 0xe3, 0x08, 0xc5, 0x2c, 0xbe, 0xc2, 0xe2, 0x4d, 0x97, 0xf1, 0xfd, 0xab, 0xeb, 0x91, 0x74, + 0x75, 0x3d, 0x92, 0xfe, 0xbd, 0x1e, 0x49, 0x2f, 0x6f, 0x46, 0xad, 0xab, 0x9b, 0x51, 0xeb, 0xef, + 0x9b, 0x51, 0xeb, 0xf9, 0x17, 0x8d, 0xa6, 0x72, 0x31, 0x89, 0x30, 0xd1, 0x91, 0xe3, 0x1e, 0xf8, + 0x58, 0x9f, 0x1f, 0xea, 0x11, 0xf6, 0xb2, 0x10, 0x12, 0xfe, 0x25, 0x72, 0x50, 0x7e, 0x8a, 0x7c, + 0x3a, 0x39, 0x10, 0x5f, 0x23, 0x6c, 0x9f, 0xce, 0x2a, 0x1b, 0x20, 0x87, 0xaf, 0x03, 0x00, 0x00, + 0xff, 0xff, 0xe7, 0x69, 0x57, 0xd0, 0xb5, 0x08, 0x00, 0x00, } func (m *ClientState) Marshal() (dAtA []byte, err error) { @@ -415,26 +404,6 @@ func (m *ClientState) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = 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]) @@ -771,12 +740,6 @@ func (m *ClientState) Size() (n int) { n += 1 + l + sovDymint(uint64(l)) } } - if m.AllowUpdateAfterExpiry { - n += 2 - } - if m.AllowUpdateAfterMisbehaviour { - n += 2 - } return n } @@ -1187,46 +1150,6 @@ func (m *ClientState) Unmarshal(dAtA []byte) error { } 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 ErrIntOverflowDymint - } - 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 ErrIntOverflowDymint - } - 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 := skipDymint(dAtA[iNdEx:]) diff --git a/modules/light-clients/01-dymint/types/fraction.go b/modules/light-clients/01-dymint/types/fraction.go deleted file mode 100644 index 8e1e2413afd..00000000000 --- a/modules/light-clients/01-dymint/types/fraction.go +++ /dev/null @@ -1,21 +0,0 @@ -package types - -import ( - tmmath "github.com/tendermint/tendermint/libs/math" -) - -// NewFractionFromTm returns a new Fraction instance from a tmmath.Fraction -func NewFractionFromTm(f tmmath.Fraction) Fraction { - return Fraction{ - Numerator: f.Numerator, - Denominator: f.Denominator, - } -} - -// ToDymint converts Fraction to tmmath.Fraction -func (f Fraction) ToDymint() tmmath.Fraction { - return tmmath.Fraction{ - Numerator: f.Numerator, - Denominator: f.Denominator, - } -} diff --git a/modules/light-clients/01-dymint/types/header.go b/modules/light-clients/01-dymint/types/header.go index 32d4077755f..40a57dc668c 100644 --- a/modules/light-clients/01-dymint/types/header.go +++ b/modules/light-clients/01-dymint/types/header.go @@ -101,6 +101,10 @@ func (h Header) ValidateCommit() (err error) { return sdkerrors.Wrap(err, "validator set is not dymint validator set type") } + if tmValset.Size() != len(tmCommit.Signatures) { + return tmtypes.NewErrInvalidCommitSignatures(tmValset.Size(), len(tmCommit.Signatures)) + } + if !blockID.Equals(tmCommit.BlockID) { return fmt.Errorf("invalid commit -- wrong block ID: want %v, got %v", blockID, tmCommit.BlockID) @@ -125,6 +129,9 @@ func (h Header) ValidateCommit() (err error) { if !val.PubKey.VerifySignature(headerBytes, commitSig.Signature) { return fmt.Errorf("wrong signature (#%d): %X", valIdx, commitSig.Signature) } + } else { + return fmt.Errorf("proposer is not in the validator set (proposer: %x)", h.Header.ProposerAddress) + } return nil diff --git a/modules/light-clients/01-dymint/types/header_test.go b/modules/light-clients/01-dymint/types/header_test.go index 02aea15c969..b0f55e1853e 100644 --- a/modules/light-clients/01-dymint/types/header_test.go +++ b/modules/light-clients/01-dymint/types/header_test.go @@ -4,6 +4,7 @@ import ( "time" tmprotocrypto "github.com/tendermint/tendermint/proto/tendermint/crypto" + tmprototypes "github.com/tendermint/tendermint/proto/tendermint/types" clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" "github.com/cosmos/ibc-go/v3/modules/core/exported" @@ -66,6 +67,16 @@ func (suite *DymintTestSuite) TestHeaderValidateBasic() { {"ValidatorSetFromProto failed", func() { header.ValidatorSet.Validators[0].PubKey = tmprotocrypto.PublicKey{} }, false}, + {"header validator hash does not equal hash of validator set", func() { + // generated new validator set + val := tmprototypes.Validator{} + valSet := tmprototypes.ValidatorSet{ + Validators: []*tmprototypes.Validator{&val}, + Proposer: &val, + TotalVotingPower: 0, + } + header.ValidatorSet = &valSet + }, false}, } suite.Require().Equal(exported.Dymint, suite.header.ClientType()) diff --git a/modules/light-clients/01-dymint/types/misbehaviour_handle_test.go b/modules/light-clients/01-dymint/types/misbehaviour_handle_test.go index bec054db940..50be1356980 100644 --- a/modules/light-clients/01-dymint/types/misbehaviour_handle_test.go +++ b/modules/light-clients/01-dymint/types/misbehaviour_handle_test.go @@ -54,7 +54,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }{ { "valid fork misbehaviour", - types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), @@ -69,7 +69,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "valid time misbehaviour", - types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), @@ -84,7 +84,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "valid time misbehaviour header 1 stricly less than header 2", - types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), @@ -99,7 +99,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "valid misbehavior at height greater than last consensusState", - types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), heightMinus1, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), @@ -114,7 +114,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "valid misbehaviour with different trusted heights", - types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), heightMinus1, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), @@ -129,7 +129,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "valid misbehaviour at a previous revision", - types.NewClientState(chainIDRevision1, trustingPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainIDRevision1, trustingPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), upgradePath), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), heightMinus1, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), @@ -144,7 +144,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "valid misbehaviour at a future revision", - types.NewClientState(chainIDRevision0, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainIDRevision0, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), heightMinus1, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), @@ -159,7 +159,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "valid misbehaviour with trusted heights at a previous revision", - types.NewClientState(chainIDRevision1, trustingPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainIDRevision1, trustingPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), upgradePath), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), heightMinus1, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), @@ -174,7 +174,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "consensus state's valset hash different from misbehaviour should still pass", - types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), height, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), @@ -189,7 +189,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "invalid fork misbehaviour: identical headers", - types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), @@ -204,7 +204,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "invalid time misbehaviour: monotonically increasing time", - types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), @@ -219,7 +219,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "invalid misbehavior misbehaviour from different chain", - types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), @@ -234,7 +234,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "invalid misbehavior misbehaviour with trusted height different from trusted consensus state", - types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), heightMinus1, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), suite.valsHash), @@ -264,7 +264,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "trusted consensus state does not exist", - types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), nil, // consensus state for trusted height - 1 does not exist in store clienttypes.Height{}, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), @@ -279,7 +279,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "invalid dymint misbehaviour", - types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), @@ -290,7 +290,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "provided height > header height", - types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), height, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), @@ -305,7 +305,7 @@ func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { }, { "trusting period expired", - types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false), + types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), types.NewConsensusState(time.Time{}, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), heightMinus1, types.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot(tmhash.Sum([]byte("app_hash"))), bothValsHash), diff --git a/modules/light-clients/01-dymint/types/proposal_handle.go b/modules/light-clients/01-dymint/types/proposal_handle.go index bfa7f242e92..a45053ed34c 100644 --- a/modules/light-clients/01-dymint/types/proposal_handle.go +++ b/modules/light-clients/01-dymint/types/proposal_handle.go @@ -2,6 +2,7 @@ package types import ( "reflect" + "time" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -12,18 +13,17 @@ import ( ) // CheckSubstituteAndUpdateState will try to update the client with the state of the -// substitute if and only if the proposal passes and one of the following conditions are -// satisfied: -// 1) AllowUpdateAfterMisbehaviour and Status() == Frozen -// 2) AllowUpdateAfterExpiry=true and Status() == Expired +// 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) +// - 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. If a client is frozen and AllowUpdateAfterMisbehaviour -// is set to true, the client will be unexpired even if AllowUpdateAfterExpiry is set to false. +// the FrozenHeight to the zero Height. func (cs ClientState) CheckSubstituteAndUpdateState( ctx sdk.Context, cdc codec.BinaryCodec, subjectClientStore, substituteClientStore sdk.KVStore, substituteClient exported.ClientState, @@ -39,23 +39,9 @@ func (cs ClientState) CheckSubstituteAndUpdateState( return nil, sdkerrors.Wrap(clienttypes.ErrInvalidSubstitute, "subject client state does not match substitute client state") } - switch cs.Status(ctx, subjectClientStore, cdc) { - - case exported.Frozen: - if !cs.AllowUpdateAfterMisbehaviour { - return nil, sdkerrors.Wrap(clienttypes.ErrUpdateClientFailed, "client is not allowed to be unfrozen") - } - + if cs.Status(ctx, subjectClientStore, cdc) == exported.Frozen { // unfreeze the client cs.FrozenHeight = clienttypes.ZeroHeight() - - case exported.Expired: - if !cs.AllowUpdateAfterExpiry { - return nil, sdkerrors.Wrap(clienttypes.ErrUpdateClientFailed, "client is not allowed to be unexpired") - } - - default: - return nil, sdkerrors.Wrap(clienttypes.ErrUpdateClientFailed, "client cannot be updated with proposal") } // copy consensus states and processed time from substitute to subject @@ -85,6 +71,9 @@ func (cs ClientState) CheckSubstituteAndUpdateState( 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. @@ -92,13 +81,15 @@ func (cs ClientState) CheckSubstituteAndUpdateState( } // IsMatchingClientState returns true if all the client state parameters match -// except for frozen height, latest height, and chain-id. +// 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 = "" diff --git a/modules/light-clients/01-dymint/types/proposal_handle_test.go b/modules/light-clients/01-dymint/types/proposal_handle_test.go index e295e778868..1e8a521ff81 100644 --- a/modules/light-clients/01-dymint/types/proposal_handle_test.go +++ b/modules/light-clients/01-dymint/types/proposal_handle_test.go @@ -11,9 +11,7 @@ import ( ibctesting "github.com/cosmos/ibc-go/v3/testing" ) -var ( - frozenHeight = clienttypes.NewHeight(0, 1) -) +var frozenHeight = clienttypes.NewHeight(0, 1) func (suite *DymintTestSuite) TestCheckSubstituteUpdateStateBasic() { var ( @@ -32,21 +30,23 @@ func (suite *DymintTestSuite) TestCheckSubstituteUpdateStateBasic() { { "non-matching substitute", func() { suite.coordinator.SetupClients(substitutePath) - substituteClientState = suite.chainA.GetClientState(substitutePath.EndpointA.ClientID) - switch substituteClientState.ClientType() { - case exported.Dymint: - dmClientState, ok := substituteClientState.(*types.ClientState) - suite.Require().True(ok) - - dmClientState.ChainId = dmClientState.ChainId + "different chain" - case exported.Tendermint: - tmClientState, ok := substituteClientState.(*tmtypes.ClientState) - suite.Require().True(ok) - - tmClientState.ChainId = tmClientState.ChainId + "different chain" - default: - panic(fmt.Sprintf("client type %s is not supported", substituteClientState.ClientType())) - } + //substituteClientState = suite.chainA.GetClientState(substitutePath.EndpointA.ClientID) + // switch substituteClientState.ClientType() { + // case exported.Dymint: + // dmClientState, ok := substituteClientState.(*types.ClientState) + // suite.Require().True(ok) + // // change trusting period so that test should fail + // dmClientState.TrustingPeriod = time.Hour * 24 * 7 + // dmClientState.ChainId = dmClientState.ChainId + "different chain" + // case exported.Tendermint: + // tmClientState, ok := substituteClientState.(*tmtypes.ClientState) + // suite.Require().True(ok) + // // change trusting period so that test should fail + // tmClientState.TrustingPeriod = time.Hour * 24 * 7 + // tmClientState.ChainId = tmClientState.ChainId + "different chain" + // default: + // panic(fmt.Sprintf("client type %s is not supported", substituteClientState.ClientType())) + // } }, }, } @@ -65,14 +65,10 @@ func (suite *DymintTestSuite) TestCheckSubstituteUpdateStateBasic() { switch subjectClientState.ClientType() { case exported.Dymint: subjectDMClientState := subjectClientState.(*types.ClientState) - subjectDMClientState.AllowUpdateAfterMisbehaviour = true - subjectDMClientState.AllowUpdateAfterExpiry = true // expire subject client suite.coordinator.IncrementTimeBy(subjectDMClientState.TrustingPeriod) case exported.Tendermint: subjectTMClientState := subjectClientState.(*tmtypes.ClientState) - subjectTMClientState.AllowUpdateAfterMisbehaviour = true - subjectTMClientState.AllowUpdateAfterExpiry = true // expire subject client suite.coordinator.IncrementTimeBy(subjectTMClientState.TrustingPeriod) default: @@ -97,140 +93,40 @@ func (suite *DymintTestSuite) TestCheckSubstituteUpdateStateBasic() { // this is to prevent headers from failing when attempting to update later. func (suite *DymintTestSuite) TestCheckSubstituteAndUpdateState() { testCases := []struct { - name string - AllowUpdateAfterExpiry bool - AllowUpdateAfterMisbehaviour bool - FreezeClient bool - ExpireClient bool - expPass bool + name string + FreezeClient bool + ExpireClient bool + expPass bool }{ { - name: "not allowed to be updated, not frozen or expired", - AllowUpdateAfterExpiry: false, - AllowUpdateAfterMisbehaviour: false, - FreezeClient: false, - ExpireClient: false, - expPass: false, - }, - { - name: "not allowed to be updated, client is frozen", - AllowUpdateAfterExpiry: false, - AllowUpdateAfterMisbehaviour: false, - FreezeClient: true, - ExpireClient: false, - expPass: false, - }, - { - name: "not allowed to be updated, client is expired", - AllowUpdateAfterExpiry: false, - AllowUpdateAfterMisbehaviour: false, - FreezeClient: false, - ExpireClient: true, - expPass: false, - }, - { - name: "not allowed to be updated, client is frozen and expired", - AllowUpdateAfterExpiry: false, - AllowUpdateAfterMisbehaviour: false, - FreezeClient: true, - ExpireClient: true, - expPass: false, - }, - { - name: "allowed to be updated only after misbehaviour, not frozen or expired", - AllowUpdateAfterExpiry: false, - AllowUpdateAfterMisbehaviour: true, - FreezeClient: false, - ExpireClient: false, - expPass: false, - }, - { - name: "allowed to be updated only after misbehaviour, client is expired", - AllowUpdateAfterExpiry: false, - AllowUpdateAfterMisbehaviour: true, - FreezeClient: false, - ExpireClient: true, - expPass: false, - }, - { - name: "allowed to be updated only after expiry, not frozen or expired", - AllowUpdateAfterExpiry: true, - AllowUpdateAfterMisbehaviour: false, - FreezeClient: false, - ExpireClient: false, - expPass: false, - }, - { - name: "allowed to be updated only after expiry, client is frozen", - AllowUpdateAfterExpiry: true, - AllowUpdateAfterMisbehaviour: false, - FreezeClient: true, - ExpireClient: false, - expPass: false, - }, - { - name: "PASS: allowed to be updated only after misbehaviour, client is frozen", - AllowUpdateAfterExpiry: false, - AllowUpdateAfterMisbehaviour: true, - FreezeClient: true, - ExpireClient: false, - expPass: true, - }, - { - name: "PASS: allowed to be updated only after misbehaviour, client is frozen and expired", - AllowUpdateAfterExpiry: false, - AllowUpdateAfterMisbehaviour: true, - FreezeClient: true, - ExpireClient: true, - expPass: true, - }, - { - name: "PASS: allowed to be updated only after expiry, client is expired", - AllowUpdateAfterExpiry: true, - AllowUpdateAfterMisbehaviour: false, - FreezeClient: false, - ExpireClient: true, - expPass: true, + name: "PASS: update checks are deprecated, client is frozen and expired", + FreezeClient: true, + ExpireClient: true, + expPass: true, }, { - name: "allowed to be updated only after expiry, client is frozen and expired", - AllowUpdateAfterExpiry: true, - AllowUpdateAfterMisbehaviour: false, - FreezeClient: true, - ExpireClient: true, - expPass: false, + name: "PASS: update checks are deprecated, not frozen or expired", + FreezeClient: false, + ExpireClient: false, + expPass: true, }, { - name: "allowed to be updated after expiry and misbehaviour, not frozen or expired", - AllowUpdateAfterExpiry: true, - AllowUpdateAfterMisbehaviour: true, - FreezeClient: false, - ExpireClient: false, - expPass: false, + name: "PASS: update checks are deprecated, not frozen or expired", + FreezeClient: false, + ExpireClient: false, + expPass: true, }, { - name: "PASS: allowed to be updated after expiry and misbehaviour, client is frozen", - AllowUpdateAfterExpiry: true, - AllowUpdateAfterMisbehaviour: true, - FreezeClient: true, - ExpireClient: false, - expPass: true, + name: "PASS: update checks are deprecated, client is frozen", + FreezeClient: true, + ExpireClient: false, + expPass: true, }, { - name: "PASS: allowed to be updated after expiry and misbehaviour, client is expired", - AllowUpdateAfterExpiry: true, - AllowUpdateAfterMisbehaviour: true, - FreezeClient: false, - ExpireClient: true, - expPass: true, - }, - { - name: "PASS: allowed to be updated after expiry and misbehaviour, client is frozen and expired", - AllowUpdateAfterExpiry: true, - AllowUpdateAfterMisbehaviour: true, - FreezeClient: true, - ExpireClient: true, - expPass: true, + name: "PASS: update checks are deprecated, client is expired", + FreezeClient: false, + ExpireClient: true, + expPass: true, }, } @@ -241,7 +137,6 @@ func (suite *DymintTestSuite) TestCheckSubstituteAndUpdateState() { // a client are each tested to ensure that unexpiry headers cannot update // a client when a unfreezing header is required. suite.Run(tc.name, func() { - // start by testing unexpiring the client suite.SetupTest() // reset @@ -253,8 +148,6 @@ func (suite *DymintTestSuite) TestCheckSubstituteAndUpdateState() { switch subjectClientState.ClientType() { case exported.Dymint: subjectDMClientState := subjectClientState.(*types.ClientState) - subjectDMClientState.AllowUpdateAfterMisbehaviour = tc.AllowUpdateAfterMisbehaviour - subjectDMClientState.AllowUpdateAfterExpiry = tc.AllowUpdateAfterExpiry // apply freezing or expiry as determined by the test case if tc.FreezeClient { @@ -267,8 +160,6 @@ func (suite *DymintTestSuite) TestCheckSubstituteAndUpdateState() { } case exported.Tendermint: subjectTMClientState := subjectClientState.(*tmtypes.ClientState) - subjectTMClientState.AllowUpdateAfterMisbehaviour = tc.AllowUpdateAfterMisbehaviour - subjectTMClientState.AllowUpdateAfterExpiry = tc.AllowUpdateAfterExpiry // apply freezing or expiry as determined by the test case if tc.FreezeClient { @@ -297,12 +188,12 @@ func (suite *DymintTestSuite) TestCheckSubstituteAndUpdateState() { switch substituteClientState.ClientType() { case exported.Dymint: substituteDMClientState := substituteClientState.(*types.ClientState) - substituteDMClientState.AllowUpdateAfterMisbehaviour = tc.AllowUpdateAfterMisbehaviour - substituteDMClientState.AllowUpdateAfterExpiry = tc.AllowUpdateAfterExpiry + // update trusting period of substitute client state + substituteDMClientState.TrustingPeriod = time.Hour * 24 * 7 case exported.Tendermint: substituteTMClientState := substituteClientState.(*tmtypes.ClientState) - substituteTMClientState.AllowUpdateAfterMisbehaviour = tc.AllowUpdateAfterMisbehaviour - substituteTMClientState.AllowUpdateAfterExpiry = tc.AllowUpdateAfterExpiry + // update trusting period of substitute client state + substituteTMClientState.TrustingPeriod = time.Hour * 24 * 7 default: panic(fmt.Sprintf("client type %s is not supported", subjectClientState.ClientType())) } @@ -349,17 +240,19 @@ func (suite *DymintTestSuite) TestCheckSubstituteAndUpdateState() { updatedClientChainId := newChainID FrozenHeight := clienttypes.ZeroHeight() + TrustingPeriod := time.Hour * 24 * 7 switch updatedClient.ClientType() { case exported.Dymint: updatedClientChainId = updatedClient.(*types.ClientState).ChainId FrozenHeight = updatedClient.(*types.ClientState).FrozenHeight + TrustingPeriod = updatedClient.(*types.ClientState).TrustingPeriod case exported.Tendermint: updatedClientChainId = updatedClient.(*tmtypes.ClientState).ChainId FrozenHeight = updatedClient.(*tmtypes.ClientState).FrozenHeight + TrustingPeriod = updatedClient.(*tmtypes.ClientState).TrustingPeriod default: panic(fmt.Sprintf("client type %s is not supported", subjectClientState.ClientType())) } - suite.Require().Equal(newChainID, updatedClientChainId) suite.Require().Equal(clienttypes.ZeroHeight(), FrozenHeight) subjectClientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), subjectPath.EndpointA.ClientID) @@ -378,6 +271,8 @@ func (suite *DymintTestSuite) TestCheckSubstituteAndUpdateState() { suite.Require().Equal(expectedProcessedHeight, subjectProcessedHeight) suite.Require().Equal(expectedIterationKey, subjectIterationKey) + suite.Require().Equal(newChainID, updatedClientChainId) + suite.Require().Equal(time.Hour*24*7, TrustingPeriod) } else { suite.Require().Error(err) suite.Require().Nil(updatedClient) @@ -433,9 +328,15 @@ func (suite *DymintTestSuite) TestIsMatchingClientState() { }, true, }, { - "not matching, trusting period is different", func() { + "matching, trusting period is different", func() { subjectClientState.TrustingPeriod = time.Duration(time.Hour * 10) substituteClientState.TrustingPeriod = time.Duration(time.Hour * 1) + }, true, + }, + { + "not matching, trust level is different", func() { + subjectClientState.TrustLevel = types.Fraction{2, 3} + substituteClientState.TrustLevel = types.Fraction{1, 3} }, false, }, } @@ -454,7 +355,6 @@ func (suite *DymintTestSuite) TestIsMatchingClientState() { tc.malleate() suite.Require().Equal(tc.expPass, types.IsMatchingClientState(*subjectClientState, *substituteClientState)) - }) } } diff --git a/modules/light-clients/01-dymint/types/store.go b/modules/light-clients/01-dymint/types/store.go index 0a9a61dfcf5..ca701b1eb85 100644 --- a/modules/light-clients/01-dymint/types/store.go +++ b/modules/light-clients/01-dymint/types/store.go @@ -96,7 +96,6 @@ func IterateConsensusMetadata(store sdk.KVStore, cb func(key, val []byte) bool) if len(keySplit) != 3 { // ignore all consensus state keys continue - } if keySplit[2] != "processedTime" && keySplit[2] != "processedHeight" { diff --git a/modules/light-clients/01-dymint/types/update.go b/modules/light-clients/01-dymint/types/update.go index 60f18f484c1..8c9162b85fc 100644 --- a/modules/light-clients/01-dymint/types/update.go +++ b/modules/light-clients/01-dymint/types/update.go @@ -204,6 +204,11 @@ func checkValidity( return sdkerrors.Wrap(err, "signed header in not dymint signed header type") } + _, err = tmtypes.ValidatorSetFromProto(header.ValidatorSet) + if err != nil { + return sdkerrors.Wrap(err, "validator set in not dymint validator set type") + } + // assert header height is newer than consensus state if header.GetHeight().LTE(header.TrustedHeight) { return sdkerrors.Wrapf( diff --git a/modules/light-clients/01-dymint/types/update_test.go b/modules/light-clients/01-dymint/types/update_test.go index 37aac0ef770..6c23b8eadd9 100644 --- a/modules/light-clients/01-dymint/types/update_test.go +++ b/modules/light-clients/01-dymint/types/update_test.go @@ -52,7 +52,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "successful update with next height and same validator set", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) currentTime = suite.now @@ -63,7 +63,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "successful update with future height and different validator set", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus5.RevisionHeight), height, suite.headerTime, bothValSet, suite.valSet, bothSigners) currentTime = suite.now @@ -74,7 +74,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "successful update with next height and different validator set", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), bothValSet.Hash()) newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, bothValSet, bothValSet, bothSigners) currentTime = suite.now @@ -85,7 +85,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "successful update for a previous height", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) consStateHeight = heightMinus3 newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightMinus1.RevisionHeight), heightMinus3, suite.headerTime, bothValSet, suite.valSet, bothSigners) @@ -97,7 +97,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "successful update for a previous revision", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainIDRevision1, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainIDRevision1, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) consStateHeight = heightMinus3 newHeader = chainADymint.CreateDMClientHeader(chainIDRevision0, int64(height.RevisionHeight), heightMinus3, suite.headerTime, bothValSet, suite.valSet, bothSigners) @@ -108,7 +108,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "successful update with identical header to a previous update", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, heightPlus1, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, heightPlus1, commitmenttypes.GetSDKSpecs(), upgradePath) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) currentTime = suite.now @@ -122,7 +122,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "misbehaviour detection: header conflicts with existing consensus state", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, heightPlus1, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, heightPlus1, commitmenttypes.GetSDKSpecs(), upgradePath) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) currentTime = suite.now @@ -138,7 +138,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "misbehaviour detection: previous consensus state time is not before header time. time monotonicity violation", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath) // create an intermediate consensus state with the same time as the newHeader to create a time violation. // header time is after client time consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) @@ -156,7 +156,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "misbehaviour detection: next consensus state time is not after header time. time monotonicity violation", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath) // create the next consensus state with the same time as the intermediate newHeader to create a time violation. // header time is after clientTime consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) @@ -174,7 +174,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "unsuccessful update with incorrect header chain-id", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) newHeader = chainADymint.CreateDMClientHeader("ethermint", int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) currentTime = suite.now @@ -185,7 +185,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "unsuccessful update to a future revision", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainIDRevision0, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainIDRevision0, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) newHeader = chainADymint.CreateDMClientHeader(chainIDRevision1, 1, height, suite.headerTime, suite.valSet, suite.valSet, signers) currentTime = suite.now @@ -195,7 +195,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "unsuccessful update: header height revision and trusted height revision mismatch", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainIDRevision1, trustingPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainIDRevision1, trustingPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), upgradePath) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) newHeader = chainADymint.CreateDMClientHeader(chainIDRevision1, 3, height, suite.headerTime, suite.valSet, suite.valSet, signers) currentTime = suite.now @@ -206,7 +206,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "unsuccessful update: trusting period has passed since last client timestamp", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) // make current time pass trusting period from last timestamp on clientstate @@ -218,7 +218,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "unsuccessful update: header timestamp is past current timestamp", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.now.Add(time.Minute), suite.valSet, suite.valSet, signers) currentTime = suite.now @@ -229,7 +229,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "unsuccessful update: header timestamp is not past last client timestamp", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.clientTime, suite.valSet, suite.valSet, signers) currentTime = suite.now @@ -240,7 +240,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "header basic validation failed", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) // cause new header to fail validatebasic by changing commit height to mismatch header height @@ -253,7 +253,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "header height < consensus height", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, clienttypes.NewHeight(height.RevisionNumber, heightPlus5.RevisionHeight), commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, clienttypes.NewHeight(height.RevisionNumber, heightPlus5.RevisionHeight), commitmenttypes.GetSDKSpecs(), upgradePath) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) // Make new header at height less than latest client state newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightMinus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) @@ -265,7 +265,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "proposer is not in the validator set", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) newValSet := suite.valSet newValSet.Proposer = altVal @@ -278,7 +278,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "wrong proposer address", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) newHeader.SignedHeader.Commit.Signatures[0].ValidatorAddress = altVal.Address @@ -290,7 +290,7 @@ func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { { name: "wrong proposer signature", setup: func(suite *DymintTestSuite) { - clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + clientState = types.NewClientState(chainID, trustingPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath) consensusState = types.NewConsensusState(suite.clientTime, commitmenttypes.NewMerkleRoot(suite.header.Header.GetAppHash()), suite.valsHash) newHeader = chainADymint.CreateDMClientHeader(chainID, int64(heightPlus1.RevisionHeight), height, suite.headerTime, suite.valSet, suite.valSet, signers) newHeader.SignedHeader.Commit.Signatures[0].Signature = []byte{123} diff --git a/modules/light-clients/01-dymint/types/upgrade.go b/modules/light-clients/01-dymint/types/upgrade.go index 76153dfb708..a1fd328bd66 100644 --- a/modules/light-clients/01-dymint/types/upgrade.go +++ b/modules/light-clients/01-dymint/types/upgrade.go @@ -101,7 +101,6 @@ func (cs ClientState) VerifyUpgradeAndUpdateState( newClientState := NewClientState( tmUpgradeClient.ChainId, cs.TrustingPeriod, cs.MaxClockDrift, tmUpgradeClient.LatestHeight, tmUpgradeClient.ProofSpecs, tmUpgradeClient.UpgradePath, - cs.AllowUpdateAfterExpiry, cs.AllowUpdateAfterMisbehaviour, ) if err := newClientState.Validate(); err != nil { diff --git a/modules/light-clients/01-dymint/types/upgrade_test.go b/modules/light-clients/01-dymint/types/upgrade_test.go index 8a3d8938f0e..f8e98920956 100644 --- a/modules/light-clients/01-dymint/types/upgrade_test.go +++ b/modules/light-clients/01-dymint/types/upgrade_test.go @@ -61,7 +61,7 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { setup: func() { upgradedHeight := clienttypes.NewHeight(0, uint64(dymintChain.GetContext().BlockHeight()+2)) // don't use -1 suffix in chain id - upgradedClient = types.NewClientState("newChainId", trustingPeriod, maxClockDrift, upgradedHeight, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + upgradedClient = types.NewClientState("newChainId", trustingPeriod, maxClockDrift, upgradedHeight, commitmenttypes.GetSDKSpecs(), upgradePath) upgradedClient = upgradedClient.ZeroCustomFields() upgradedClientBz, err = clienttypes.MarshalClientState(dymintCounterpartyChain.App.AppCodec(), upgradedClient) suite.Require().NoError(err) @@ -116,7 +116,7 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { name: "unsuccessful upgrade: committed client does not have zeroed custom fields", setup: func() { // non-zeroed upgrade client - upgradedClient = types.NewClientState(newChainId, trustingPeriod, maxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + upgradedClient = types.NewClientState(newChainId, trustingPeriod, maxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), upgradePath) upgradedClientBz, err = clienttypes.MarshalClientState(dymintCounterpartyChain.App.AppCodec(), upgradedClient) suite.Require().NoError(err) @@ -152,7 +152,7 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) // change upgradedClient client-specified parameters - upgradedClient = types.NewClientState("wrongchainID", trustingPeriod, maxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), upgradePath, true, true) + upgradedClient = types.NewClientState("wrongchainID", trustingPeriod, maxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), upgradePath) suite.coordinator.CommitBlock(dymintChain) err := endpoint.UpdateClient() @@ -174,7 +174,7 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { dymintChain.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(dymintChain.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) // change upgradedClient client-specified parameters - upgradedClient = types.NewClientState(newChainId, ubdPeriod+trustingPeriod, maxClockDrift+5, lastHeight, commitmenttypes.GetSDKSpecs(), upgradePath, true, false) + upgradedClient = types.NewClientState(newChainId, ubdPeriod+trustingPeriod, maxClockDrift+5, lastHeight, commitmenttypes.GetSDKSpecs(), upgradePath) suite.coordinator.CommitBlock(dymintChain) err := endpoint.UpdateClient() @@ -405,7 +405,7 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { 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 = types.NewClientState(newChainId, trustingPeriod, maxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + upgradedClient = types.NewClientState(newChainId, trustingPeriod, maxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), upgradePath) upgradedClientBz, err = clienttypes.MarshalClientState(dymintCounterpartyChain.App.AppCodec(), upgradedClient) suite.Require().NoError(err) @@ -451,7 +451,7 @@ func (suite *DymintTestSuite) TestVerifyUpgrade() { endpoint = path.EndpointA } - upgradedClient = types.NewClientState(newChainId, trustingPeriod, maxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), upgradePath, false, false) + upgradedClient = types.NewClientState(newChainId, trustingPeriod, maxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), upgradePath) upgradedClient = upgradedClient.ZeroCustomFields() upgradedClientBz, err = clienttypes.MarshalClientState(dymintCounterpartyChain.App.AppCodec(), upgradedClient) suite.Require().NoError(err) diff --git a/modules/light-clients/06-solomachine/types/client_state_test.go b/modules/light-clients/06-solomachine/types/client_state_test.go index 09ea9693119..16b62b2722b 100644 --- a/modules/light-clients/06-solomachine/types/client_state_test.go +++ b/modules/light-clients/06-solomachine/types/client_state_test.go @@ -82,7 +82,6 @@ func (suite *SoloMachineTestSuite) TestClientStateValidateBasic() { tc := tc suite.Run(tc.name, func() { - err := tc.clientState.Validate() if tc.expPass { @@ -238,7 +237,6 @@ func (suite *SoloMachineTestSuite) TestVerifyClientState() { tc := tc suite.Run(tc.name, func() { - var expSeq uint64 if tc.clientState.ConsensusState != nil { expSeq = tc.clientState.Sequence + 1 @@ -359,7 +357,6 @@ func (suite *SoloMachineTestSuite) TestVerifyClientConsensusState() { tc := tc suite.Run(tc.name, func() { - var expSeq uint64 if tc.clientState.ConsensusState != nil { expSeq = tc.clientState.Sequence + 1 diff --git a/modules/light-clients/06-solomachine/types/codec_test.go b/modules/light-clients/06-solomachine/types/codec_test.go index 1a0e3e0086f..0ed8760daa3 100644 --- a/modules/light-clients/06-solomachine/types/codec_test.go +++ b/modules/light-clients/06-solomachine/types/codec_test.go @@ -55,7 +55,6 @@ func (suite SoloMachineTestSuite) TestUnmarshalDataByType() { path := solomachine.GetConsensusStatePath(counterpartyClientIdentifier, clienttypes.NewHeight(0, 5)) data, err = types.ConsensusStateDataBytes(cdc, path, solomachine.ConsensusState()) suite.Require().NoError(err) - }, true, }, { @@ -73,7 +72,6 @@ func (suite SoloMachineTestSuite) TestUnmarshalDataByType() { data, err = types.ConnectionStateDataBytes(cdc, path, conn) suite.Require().NoError(err) - }, true, }, { @@ -104,7 +102,6 @@ func (suite SoloMachineTestSuite) TestUnmarshalDataByType() { data, err = types.ConnectionStateDataBytes(cdc, path, conn) suite.Require().NoError(err) - }, false, }, { @@ -186,5 +183,4 @@ func (suite SoloMachineTestSuite) TestUnmarshalDataByType() { }) } } - } diff --git a/modules/light-clients/06-solomachine/types/consensus_state_test.go b/modules/light-clients/06-solomachine/types/consensus_state_test.go index 33e200c5461..2bb1ab5a456 100644 --- a/modules/light-clients/06-solomachine/types/consensus_state_test.go +++ b/modules/light-clients/06-solomachine/types/consensus_state_test.go @@ -61,7 +61,6 @@ func (suite *SoloMachineTestSuite) TestConsensusStateValidateBasic() { tc := tc suite.Run(tc.name, func() { - err := tc.consensusState.ValidateBasic() if tc.expPass { diff --git a/modules/light-clients/06-solomachine/types/misbehaviour_handle.go b/modules/light-clients/06-solomachine/types/misbehaviour_handle.go index d5a1d57cb57..fb3dc573c3e 100644 --- a/modules/light-clients/06-solomachine/types/misbehaviour_handle.go +++ b/modules/light-clients/06-solomachine/types/misbehaviour_handle.go @@ -21,7 +21,6 @@ func (cs ClientState) CheckMisbehaviourAndUpdateState( clientStore sdk.KVStore, misbehaviour exported.Misbehaviour, ) (exported.ClientState, error) { - soloMisbehaviour, ok := misbehaviour.(*Misbehaviour) if !ok { return nil, sdkerrors.Wrapf( @@ -51,7 +50,6 @@ func (cs ClientState) CheckMisbehaviourAndUpdateState( // 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 verifySignatureAndData(cdc codec.BinaryCodec, clientState ClientState, misbehaviour *Misbehaviour, sigAndData *SignatureAndData) error { - // do not check misbehaviour timestamp since we want to allow processing of past misbehaviour // ensure data can be unmarshaled to the specified data type @@ -85,5 +83,4 @@ func verifySignatureAndData(cdc codec.BinaryCodec, clientState ClientState, misb } return nil - } diff --git a/modules/light-clients/06-solomachine/types/misbehaviour_handle_test.go b/modules/light-clients/06-solomachine/types/misbehaviour_handle_test.go index db58b710772..7aa19617a76 100644 --- a/modules/light-clients/06-solomachine/types/misbehaviour_handle_test.go +++ b/modules/light-clients/06-solomachine/types/misbehaviour_handle_test.go @@ -227,7 +227,6 @@ func (suite *SoloMachineTestSuite) TestCheckMisbehaviourAndUpdateState() { m.SignatureTwo.Data = msg misbehaviour = m - }, false, }, diff --git a/modules/light-clients/06-solomachine/types/misbehaviour_test.go b/modules/light-clients/06-solomachine/types/misbehaviour_test.go index 813a8520ee7..c304fc576ed 100644 --- a/modules/light-clients/06-solomachine/types/misbehaviour_test.go +++ b/modules/light-clients/06-solomachine/types/misbehaviour_test.go @@ -113,7 +113,6 @@ func (suite *SoloMachineTestSuite) TestMisbehaviourValidateBasic() { tc := tc suite.Run(tc.name, func() { - misbehaviour := solomachine.CreateMisbehaviour() tc.malleateMisbehaviour(misbehaviour) diff --git a/modules/light-clients/06-solomachine/types/proof.go b/modules/light-clients/06-solomachine/types/proof.go index f884a26d745..75a58cc3101 100644 --- a/modules/light-clients/06-solomachine/types/proof.go +++ b/modules/light-clients/06-solomachine/types/proof.go @@ -55,7 +55,8 @@ func MisbehaviourSignBytes( sequence, timestamp uint64, diversifier string, dataType DataType, - data []byte) ([]byte, error) { + data []byte, +) ([]byte, error) { signBytes := &SignBytes{ Sequence: sequence, Timestamp: timestamp, diff --git a/modules/light-clients/06-solomachine/types/proposal_handle.go b/modules/light-clients/06-solomachine/types/proposal_handle.go index a28bc27c398..c7d29faedc9 100644 --- a/modules/light-clients/06-solomachine/types/proposal_handle.go +++ b/modules/light-clients/06-solomachine/types/proposal_handle.go @@ -22,7 +22,6 @@ func (cs ClientState) CheckSubstituteAndUpdateState( ctx sdk.Context, cdc codec.BinaryCodec, subjectClientStore, _ sdk.KVStore, substituteClient exported.ClientState, ) (exported.ClientState, error) { - if !cs.AllowUpdateAfterProposal { return nil, sdkerrors.Wrapf( clienttypes.ErrUpdateClientFailed, diff --git a/modules/light-clients/06-solomachine/types/update_test.go b/modules/light-clients/06-solomachine/types/update_test.go index f13d5f198d1..ae3caa44a5c 100644 --- a/modules/light-clients/06-solomachine/types/update_test.go +++ b/modules/light-clients/06-solomachine/types/update_test.go @@ -122,7 +122,6 @@ func (suite *SoloMachineTestSuite) TestCheckHeaderAndUpdateState() { clientState = cs header = h - }, false, }, diff --git a/modules/light-clients/07-tendermint/types/client_state_test.go b/modules/light-clients/07-tendermint/types/client_state_test.go index cf52d2996b5..f45bd2fc6d6 100644 --- a/modules/light-clients/07-tendermint/types/client_state_test.go +++ b/modules/light-clients/07-tendermint/types/client_state_test.go @@ -27,9 +27,7 @@ const ( fiftyOneCharChainID = "123456789012345678901234567890123456789012345678901" ) -var ( - invalidProof = []byte("invalid proof") -) +var invalidProof = []byte("invalid proof") func (suite *TendermintTestSuite) TestStatus() { var ( @@ -166,7 +164,6 @@ func (suite *TendermintTestSuite) TestValidate() { } func (suite *TendermintTestSuite) TestInitialize() { - testCases := []struct { name string consensusState exported.ConsensusState diff --git a/modules/light-clients/07-tendermint/types/consensus_state_test.go b/modules/light-clients/07-tendermint/types/consensus_state_test.go index f45a4a5d24d..2d3be714799 100644 --- a/modules/light-clients/07-tendermint/types/consensus_state_test.go +++ b/modules/light-clients/07-tendermint/types/consensus_state_test.go @@ -14,49 +14,61 @@ func (suite *TendermintTestSuite) TestConsensusStateValidateBasic() { consensusState *types.ConsensusState expectPass bool }{ - {"success", + { + "success", &types.ConsensusState{ Timestamp: suite.now, Root: commitmenttypes.NewMerkleRoot([]byte("app_hash")), NextValidatorsHash: suite.valsHash, }, - true}, - {"success with sentinel", + true, + }, + { + "success with sentinel", &types.ConsensusState{ Timestamp: suite.now, Root: commitmenttypes.NewMerkleRoot([]byte(types.SentinelRoot)), NextValidatorsHash: suite.valsHash, }, - true}, - {"root is nil", + true, + }, + { + "root is nil", &types.ConsensusState{ Timestamp: suite.now, Root: commitmenttypes.MerkleRoot{}, NextValidatorsHash: suite.valsHash, }, - false}, - {"root is empty", + false, + }, + { + "root is empty", &types.ConsensusState{ Timestamp: suite.now, Root: commitmenttypes.MerkleRoot{}, NextValidatorsHash: suite.valsHash, }, - false}, - {"nextvalshash is invalid", + false, + }, + { + "nextvalshash is invalid", &types.ConsensusState{ Timestamp: suite.now, Root: commitmenttypes.NewMerkleRoot([]byte("app_hash")), NextValidatorsHash: []byte("hi"), }, - false}, + false, + }, - {"timestamp is zero", + { + "timestamp is zero", &types.ConsensusState{ Timestamp: time.Time{}, Root: commitmenttypes.NewMerkleRoot([]byte("app_hash")), NextValidatorsHash: suite.valsHash, }, - false}, + false, + }, } for i, tc := range testCases { diff --git a/modules/light-clients/07-tendermint/types/header_test.go b/modules/light-clients/07-tendermint/types/header_test.go index 65a122109b3..a26bd53a3ff 100644 --- a/modules/light-clients/07-tendermint/types/header_test.go +++ b/modules/light-clients/07-tendermint/types/header_test.go @@ -22,9 +22,7 @@ func (suite *TendermintTestSuite) TestGetTime() { } func (suite *TendermintTestSuite) TestHeaderValidateBasic() { - var ( - header *types.Header - ) + var header *types.Header testCases := []struct { name string malleate func() diff --git a/modules/light-clients/07-tendermint/types/misbehaviour_handle.go b/modules/light-clients/07-tendermint/types/misbehaviour_handle.go index 4c8224bde09..67f4fccdd98 100644 --- a/modules/light-clients/07-tendermint/types/misbehaviour_handle.go +++ b/modules/light-clients/07-tendermint/types/misbehaviour_handle.go @@ -101,7 +101,6 @@ func (cs ClientState) CheckMisbehaviourAndUpdateState( func checkMisbehaviourHeader( clientState *ClientState, consState *ConsensusState, header *Header, currentTimestamp time.Time, ) error { - tmTrustedValset, err := tmtypes.ValidatorSetFromProto(header.TrustedValidators) if err != nil { return sdkerrors.Wrap(err, "trusted validator set is not tendermint validator set type") diff --git a/modules/light-clients/07-tendermint/types/proposal_handle.go b/modules/light-clients/07-tendermint/types/proposal_handle.go index bfa7f242e92..09f37a2122d 100644 --- a/modules/light-clients/07-tendermint/types/proposal_handle.go +++ b/modules/light-clients/07-tendermint/types/proposal_handle.go @@ -2,6 +2,7 @@ package types import ( "reflect" + "time" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -12,18 +13,17 @@ import ( ) // CheckSubstituteAndUpdateState will try to update the client with the state of the -// substitute if and only if the proposal passes and one of the following conditions are -// satisfied: -// 1) AllowUpdateAfterMisbehaviour and Status() == Frozen -// 2) AllowUpdateAfterExpiry=true and Status() == Expired +// 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) +// - 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. If a client is frozen and AllowUpdateAfterMisbehaviour -// is set to true, the client will be unexpired even if AllowUpdateAfterExpiry is set to false. +// the FrozenHeight to the zero Height. func (cs ClientState) CheckSubstituteAndUpdateState( ctx sdk.Context, cdc codec.BinaryCodec, subjectClientStore, substituteClientStore sdk.KVStore, substituteClient exported.ClientState, @@ -39,23 +39,9 @@ func (cs ClientState) CheckSubstituteAndUpdateState( return nil, sdkerrors.Wrap(clienttypes.ErrInvalidSubstitute, "subject client state does not match substitute client state") } - switch cs.Status(ctx, subjectClientStore, cdc) { - - case exported.Frozen: - if !cs.AllowUpdateAfterMisbehaviour { - return nil, sdkerrors.Wrap(clienttypes.ErrUpdateClientFailed, "client is not allowed to be unfrozen") - } - + if cs.Status(ctx, subjectClientStore, cdc) == exported.Frozen { // unfreeze the client cs.FrozenHeight = clienttypes.ZeroHeight() - - case exported.Expired: - if !cs.AllowUpdateAfterExpiry { - return nil, sdkerrors.Wrap(clienttypes.ErrUpdateClientFailed, "client is not allowed to be unexpired") - } - - default: - return nil, sdkerrors.Wrap(clienttypes.ErrUpdateClientFailed, "client cannot be updated with proposal") } // copy consensus states and processed time from substitute to subject @@ -85,6 +71,9 @@ func (cs ClientState) CheckSubstituteAndUpdateState( 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. @@ -92,15 +81,22 @@ func (cs ClientState) CheckSubstituteAndUpdateState( } // IsMatchingClientState returns true if all the client state parameters match -// except for frozen height, latest height, and chain-id. +// 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/types/proposal_handle_test.go b/modules/light-clients/07-tendermint/types/proposal_handle_test.go index 822ec16e014..f0a76193af4 100644 --- a/modules/light-clients/07-tendermint/types/proposal_handle_test.go +++ b/modules/light-clients/07-tendermint/types/proposal_handle_test.go @@ -9,9 +9,7 @@ import ( ibctesting "github.com/cosmos/ibc-go/v3/testing" ) -var ( - frozenHeight = clienttypes.NewHeight(0, 1) -) +var frozenHeight = clienttypes.NewHeight(0, 1) func (suite *TendermintTestSuite) TestCheckSubstituteUpdateStateBasic() { var ( @@ -30,10 +28,12 @@ func (suite *TendermintTestSuite) TestCheckSubstituteUpdateStateBasic() { { "non-matching substitute", func() { suite.coordinator.SetupClients(substitutePath) - substituteClientState = suite.chainA.GetClientState(substitutePath.EndpointA.ClientID).(*types.ClientState) - tmClientState, ok := substituteClientState.(*types.ClientState) + substituteClientState, ok := suite.chainA.GetClientState(substitutePath.EndpointA.ClientID).(*types.ClientState) suite.Require().True(ok) + // change trusting period so that test should fail + substituteClientState.TrustingPeriod = time.Hour * 24 * 7 + tmClientState := substituteClientState tmClientState.ChainId = tmClientState.ChainId + "different chain" }, }, @@ -43,15 +43,12 @@ func (suite *TendermintTestSuite) TestCheckSubstituteUpdateStateBasic() { tc := tc suite.Run(tc.name, func() { - suite.SetupTest() // reset subjectPath := ibctesting.NewPath(suite.chainA, suite.chainB) substitutePath = ibctesting.NewPath(suite.chainA, suite.chainB) suite.coordinator.SetupClients(subjectPath) subjectClientState := suite.chainA.GetClientState(subjectPath.EndpointA.ClientID).(*types.ClientState) - subjectClientState.AllowUpdateAfterMisbehaviour = true - subjectClientState.AllowUpdateAfterExpiry = true // expire subject client suite.coordinator.IncrementTimeBy(subjectClientState.TrustingPeriod) @@ -73,140 +70,40 @@ func (suite *TendermintTestSuite) TestCheckSubstituteUpdateStateBasic() { // this is to prevent headers from failing when attempting to update later. func (suite *TendermintTestSuite) TestCheckSubstituteAndUpdateState() { testCases := []struct { - name string - AllowUpdateAfterExpiry bool - AllowUpdateAfterMisbehaviour bool - FreezeClient bool - ExpireClient bool - expPass bool + name string + FreezeClient bool + ExpireClient bool + expPass bool }{ { - name: "not allowed to be updated, not frozen or expired", - AllowUpdateAfterExpiry: false, - AllowUpdateAfterMisbehaviour: false, - FreezeClient: false, - ExpireClient: false, - expPass: false, - }, - { - name: "not allowed to be updated, client is frozen", - AllowUpdateAfterExpiry: false, - AllowUpdateAfterMisbehaviour: false, - FreezeClient: true, - ExpireClient: false, - expPass: false, - }, - { - name: "not allowed to be updated, client is expired", - AllowUpdateAfterExpiry: false, - AllowUpdateAfterMisbehaviour: false, - FreezeClient: false, - ExpireClient: true, - expPass: false, - }, - { - name: "not allowed to be updated, client is frozen and expired", - AllowUpdateAfterExpiry: false, - AllowUpdateAfterMisbehaviour: false, - FreezeClient: true, - ExpireClient: true, - expPass: false, - }, - { - name: "allowed to be updated only after misbehaviour, not frozen or expired", - AllowUpdateAfterExpiry: false, - AllowUpdateAfterMisbehaviour: true, - FreezeClient: false, - ExpireClient: false, - expPass: false, - }, - { - name: "allowed to be updated only after misbehaviour, client is expired", - AllowUpdateAfterExpiry: false, - AllowUpdateAfterMisbehaviour: true, - FreezeClient: false, - ExpireClient: true, - expPass: false, - }, - { - name: "allowed to be updated only after expiry, not frozen or expired", - AllowUpdateAfterExpiry: true, - AllowUpdateAfterMisbehaviour: false, - FreezeClient: false, - ExpireClient: false, - expPass: false, + name: "PASS: update checks are deprecated, client is frozen and expired", + FreezeClient: true, + ExpireClient: true, + expPass: true, }, { - name: "allowed to be updated only after expiry, client is frozen", - AllowUpdateAfterExpiry: true, - AllowUpdateAfterMisbehaviour: false, - FreezeClient: true, - ExpireClient: false, - expPass: false, + name: "PASS: update checks are deprecated, not frozen or expired", + FreezeClient: false, + ExpireClient: false, + expPass: true, }, { - name: "PASS: allowed to be updated only after misbehaviour, client is frozen", - AllowUpdateAfterExpiry: false, - AllowUpdateAfterMisbehaviour: true, - FreezeClient: true, - ExpireClient: false, - expPass: true, + name: "PASS: update checks are deprecated, not frozen or expired", + FreezeClient: false, + ExpireClient: false, + expPass: true, }, { - name: "PASS: allowed to be updated only after misbehaviour, client is frozen and expired", - AllowUpdateAfterExpiry: false, - AllowUpdateAfterMisbehaviour: true, - FreezeClient: true, - ExpireClient: true, - expPass: true, + name: "PASS: update checks are deprecated, client is frozen", + FreezeClient: true, + ExpireClient: false, + expPass: true, }, { - name: "PASS: allowed to be updated only after expiry, client is expired", - AllowUpdateAfterExpiry: true, - AllowUpdateAfterMisbehaviour: false, - FreezeClient: false, - ExpireClient: true, - expPass: true, - }, - { - name: "allowed to be updated only after expiry, client is frozen and expired", - AllowUpdateAfterExpiry: true, - AllowUpdateAfterMisbehaviour: false, - FreezeClient: true, - ExpireClient: true, - expPass: false, - }, - { - name: "allowed to be updated after expiry and misbehaviour, not frozen or expired", - AllowUpdateAfterExpiry: true, - AllowUpdateAfterMisbehaviour: true, - FreezeClient: false, - ExpireClient: false, - expPass: false, - }, - { - name: "PASS: allowed to be updated after expiry and misbehaviour, client is frozen", - AllowUpdateAfterExpiry: true, - AllowUpdateAfterMisbehaviour: true, - FreezeClient: true, - ExpireClient: false, - expPass: true, - }, - { - name: "PASS: allowed to be updated after expiry and misbehaviour, client is expired", - AllowUpdateAfterExpiry: true, - AllowUpdateAfterMisbehaviour: true, - FreezeClient: false, - ExpireClient: true, - expPass: true, - }, - { - name: "PASS: allowed to be updated after expiry and misbehaviour, client is frozen and expired", - AllowUpdateAfterExpiry: true, - AllowUpdateAfterMisbehaviour: true, - FreezeClient: true, - ExpireClient: true, - expPass: true, + name: "PASS: update checks are deprecated, client is expired", + FreezeClient: false, + ExpireClient: true, + expPass: true, }, } @@ -217,7 +114,6 @@ func (suite *TendermintTestSuite) TestCheckSubstituteAndUpdateState() { // a client are each tested to ensure that unexpiry headers cannot update // a client when a unfreezing header is required. suite.Run(tc.name, func() { - // start by testing unexpiring the client suite.SetupTest() // reset @@ -225,8 +121,6 @@ func (suite *TendermintTestSuite) TestCheckSubstituteAndUpdateState() { subjectPath := ibctesting.NewPath(suite.chainA, suite.chainB) suite.coordinator.SetupClients(subjectPath) subjectClientState := suite.chainA.GetClientState(subjectPath.EndpointA.ClientID).(*types.ClientState) - subjectClientState.AllowUpdateAfterExpiry = tc.AllowUpdateAfterExpiry - subjectClientState.AllowUpdateAfterMisbehaviour = tc.AllowUpdateAfterMisbehaviour // apply freezing or expiry as determined by the test case if tc.FreezeClient { @@ -247,8 +141,8 @@ func (suite *TendermintTestSuite) TestCheckSubstituteAndUpdateState() { substitutePath := ibctesting.NewPath(suite.chainA, suite.chainB) suite.coordinator.SetupClients(substitutePath) substituteClientState := suite.chainA.GetClientState(substitutePath.EndpointA.ClientID).(*types.ClientState) - substituteClientState.AllowUpdateAfterExpiry = tc.AllowUpdateAfterExpiry - substituteClientState.AllowUpdateAfterMisbehaviour = tc.AllowUpdateAfterMisbehaviour + // 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 @@ -299,11 +193,11 @@ func (suite *TendermintTestSuite) TestCheckSubstituteAndUpdateState() { suite.Require().Equal(expectedIterationKey, subjectIterationKey) suite.Require().Equal(newChainID, updatedClient.(*types.ClientState).ChainId) + suite.Require().Equal(time.Hour*24*7, updatedClient.(*types.ClientState).TrustingPeriod) } else { suite.Require().Error(err) suite.Require().Nil(updatedClient) } - }) } } @@ -344,9 +238,15 @@ func (suite *TendermintTestSuite) TestIsMatchingClientState() { }, true, }, { - "not matching, trusting period is different", func() { + "matching, trusting period is different", func() { subjectClientState.TrustingPeriod = time.Duration(time.Hour * 10) substituteClientState.TrustingPeriod = time.Duration(time.Hour * 1) + }, true, + }, + { + "not matching, trust level is different", func() { + subjectClientState.TrustLevel = types.Fraction{2, 3} + substituteClientState.TrustLevel = types.Fraction{1, 3} }, false, }, } @@ -365,7 +265,6 @@ func (suite *TendermintTestSuite) TestIsMatchingClientState() { tc.malleate() suite.Require().Equal(tc.expPass, types.IsMatchingClientState(*subjectClientState, *substituteClientState)) - }) } } diff --git a/modules/light-clients/07-tendermint/types/store.go b/modules/light-clients/07-tendermint/types/store.go index 785ed77ba97..5cf7f5d8657 100644 --- a/modules/light-clients/07-tendermint/types/store.go +++ b/modules/light-clients/07-tendermint/types/store.go @@ -96,7 +96,6 @@ func IterateConsensusMetadata(store sdk.KVStore, cb func(key, val []byte) bool) if len(keySplit) != 3 { // ignore all consensus state keys continue - } if keySplit[2] != "processedTime" && keySplit[2] != "processedHeight" { diff --git a/modules/light-clients/07-tendermint/types/tendermint.pb.go b/modules/light-clients/07-tendermint/types/tendermint.pb.go index 6436578b2ae..bcb71558313 100644 --- a/modules/light-clients/07-tendermint/types/tendermint.pb.go +++ b/modules/light-clients/07-tendermint/types/tendermint.pb.go @@ -59,12 +59,10 @@ type ClientState struct { // 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" yaml:"upgrade_path"` - // This flag, when set to true, will allow governance to recover a client - // which has expired - AllowUpdateAfterExpiry bool `protobuf:"varint,10,opt,name=allow_update_after_expiry,json=allowUpdateAfterExpiry,proto3" json:"allow_update_after_expiry,omitempty" yaml:"allow_update_after_expiry"` - // This flag, when set to true, will allow governance to unfreeze a client - // whose chain has experienced a misbehaviour event - AllowUpdateAfterMisbehaviour bool `protobuf:"varint,11,opt,name=allow_update_after_misbehaviour,json=allowUpdateAfterMisbehaviour,proto3" json:"allow_update_after_misbehaviour,omitempty" yaml:"allow_update_after_misbehaviour"` + // 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" yaml:"allow_update_after_expiry"` // 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" yaml:"allow_update_after_misbehaviour"` // Deprecated: Do not use. } func (m *ClientState) Reset() { *m = ClientState{} } @@ -326,73 +324,73 @@ func init() { var fileDescriptor_c6d6cf2b288949be = []byte{ // 1080 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x56, 0xcf, 0x6f, 0xe3, 0x44, - 0x14, 0x6e, 0xda, 0xb2, 0x4d, 0x26, 0xe9, 0x76, 0x31, 0xa5, 0x9b, 0x96, 0x6e, 0x1c, 0x19, 0xb4, - 0x44, 0x48, 0xb5, 0x49, 0x8a, 0x84, 0x54, 0x71, 0xc1, 0xdd, 0x45, 0x2d, 0x62, 0xa5, 0xca, 0xe5, - 0x87, 0x84, 0x84, 0xcc, 0xc4, 0x9e, 0x24, 0xa3, 0xb5, 0x3d, 0xc6, 0x33, 0x09, 0x2d, 0x7f, 0x01, - 0x1c, 0x90, 0xf6, 0x88, 0x38, 0x71, 0xe0, 0x8f, 0xd9, 0x63, 0x8f, 0x9c, 0x0c, 0x6a, 0x2f, 0x9c, - 0x73, 0xe4, 0x84, 0xe6, 0x87, 0xed, 0x69, 0xb6, 0x4b, 0xb5, 0x5c, 0xa2, 0x79, 0xef, 0x7d, 0xef, - 0xfb, 0x32, 0x6f, 0xde, 0xbc, 0x31, 0x70, 0xf0, 0x30, 0x70, 0x22, 0x3c, 0x9e, 0xb0, 0x20, 0xc2, - 0x28, 0x61, 0xd4, 0x61, 0x28, 0x09, 0x51, 0x16, 0xe3, 0x84, 0x39, 0xb3, 0xbe, 0x66, 0xd9, 0x69, - 0x46, 0x18, 0x31, 0x3a, 0x78, 0x18, 0xd8, 0x7a, 0x82, 0xad, 0x41, 0x66, 0xfd, 0x9d, 0xae, 0x96, - 0xcf, 0xce, 0x53, 0x44, 0x9d, 0x19, 0x8c, 0x70, 0x08, 0x19, 0xc9, 0x24, 0xc3, 0xce, 0xee, 0x0b, - 0x08, 0xf1, 0xab, 0xa2, 0xad, 0x34, 0x23, 0x64, 0x54, 0x58, 0x9d, 0x31, 0x21, 0xe3, 0x08, 0x39, - 0xc2, 0x1a, 0x4e, 0x47, 0x4e, 0x38, 0xcd, 0x20, 0xc3, 0x24, 0x51, 0x71, 0x73, 0x31, 0xce, 0x70, - 0x8c, 0x28, 0x83, 0x71, 0x5a, 0x00, 0xf8, 0xfe, 0x02, 0x92, 0x21, 0x47, 0xfe, 0x5d, 0xbe, 0x27, - 0xb9, 0x52, 0x80, 0x77, 0x2b, 0x00, 0x89, 0x63, 0xcc, 0xe2, 0x02, 0x54, 0x5a, 0x0a, 0xb8, 0x39, - 0x26, 0x63, 0x22, 0x96, 0x0e, 0x5f, 0x49, 0xaf, 0xf5, 0xf7, 0x1a, 0x68, 0x1e, 0x0a, 0xbe, 0x53, - 0x06, 0x19, 0x32, 0xb6, 0x41, 0x3d, 0x98, 0x40, 0x9c, 0xf8, 0x38, 0x6c, 0xd7, 0xba, 0xb5, 0x5e, - 0xc3, 0x5b, 0x13, 0xf6, 0x71, 0x68, 0x20, 0xd0, 0x64, 0xd9, 0x94, 0x32, 0x3f, 0x42, 0x33, 0x14, - 0xb5, 0x97, 0xbb, 0xb5, 0x5e, 0x73, 0xd0, 0xb3, 0xff, 0xbb, 0x9e, 0xf6, 0x27, 0x19, 0x0c, 0xf8, - 0x86, 0xdd, 0x9d, 0xe7, 0xb9, 0xb9, 0x34, 0xcf, 0x4d, 0xe3, 0x1c, 0xc6, 0xd1, 0x81, 0xa5, 0x51, - 0x59, 0x1e, 0x10, 0xd6, 0x67, 0xdc, 0x30, 0x46, 0x60, 0x43, 0x58, 0x38, 0x19, 0xfb, 0x29, 0xca, - 0x30, 0x09, 0xdb, 0x2b, 0x42, 0x6a, 0xdb, 0x96, 0xc5, 0xb2, 0x8b, 0x62, 0xd9, 0x8f, 0x54, 0x31, - 0x5d, 0x4b, 0x71, 0x6f, 0x69, 0xdc, 0x55, 0xbe, 0xf5, 0xcb, 0x9f, 0x66, 0xcd, 0xbb, 0x5b, 0x78, - 0x4f, 0x84, 0xd3, 0xc0, 0xe0, 0xde, 0x34, 0x19, 0x92, 0x24, 0xd4, 0x84, 0x56, 0x6f, 0x13, 0x7a, - 0x5b, 0x09, 0xdd, 0x97, 0x42, 0x8b, 0x04, 0x52, 0x69, 0xa3, 0x74, 0x2b, 0x29, 0x04, 0x36, 0x62, - 0x78, 0xe6, 0x07, 0x11, 0x09, 0x9e, 0xfa, 0x61, 0x86, 0x47, 0xac, 0xfd, 0xda, 0x2b, 0x6e, 0x69, - 0x21, 0x5f, 0x0a, 0xad, 0xc7, 0xf0, 0xec, 0x90, 0x3b, 0x1f, 0x71, 0x9f, 0xf1, 0x0d, 0x58, 0x1f, - 0x65, 0xe4, 0x07, 0x94, 0xf8, 0x13, 0xc4, 0x0f, 0xa4, 0x7d, 0x47, 0x88, 0xec, 0x88, 0x23, 0xe2, - 0x2d, 0x62, 0xab, 0xce, 0x99, 0xf5, 0xed, 0x23, 0x81, 0x70, 0x77, 0x95, 0xca, 0xa6, 0x54, 0xb9, - 0x96, 0x6e, 0x79, 0x2d, 0x69, 0x4b, 0x2c, 0xa7, 0x8f, 0x20, 0x43, 0x94, 0x15, 0xf4, 0x6b, 0xaf, - 0x4a, 0x7f, 0x2d, 0xdd, 0xf2, 0x5a, 0xd2, 0x56, 0xf4, 0xc7, 0xa0, 0x29, 0xae, 0x8e, 0x4f, 0x53, - 0x14, 0xd0, 0x76, 0xbd, 0xbb, 0xd2, 0x6b, 0x0e, 0xee, 0xd9, 0x38, 0xa0, 0x83, 0x7d, 0xfb, 0x84, - 0x47, 0x4e, 0x53, 0x14, 0xb8, 0x5b, 0x55, 0x0b, 0x69, 0x70, 0xcb, 0x03, 0x69, 0x01, 0xa1, 0xc6, - 0x01, 0x68, 0x4d, 0xd3, 0x71, 0x06, 0x43, 0xe4, 0xa7, 0x90, 0x4d, 0xda, 0x8d, 0xee, 0x4a, 0xaf, - 0xe1, 0xde, 0x9f, 0xe7, 0xe6, 0x1b, 0xea, 0xdc, 0xb4, 0xa8, 0xe5, 0x35, 0x95, 0x79, 0x02, 0xd9, - 0xc4, 0xf0, 0xc1, 0x36, 0x8c, 0x22, 0xf2, 0xbd, 0x3f, 0x4d, 0x43, 0xc8, 0x90, 0x0f, 0x47, 0x0c, - 0x65, 0x3e, 0x3a, 0x4b, 0x71, 0x76, 0xde, 0x06, 0xdd, 0x5a, 0xaf, 0xee, 0xbe, 0x33, 0xcf, 0xcd, - 0xae, 0x24, 0x7a, 0x29, 0xd4, 0xf2, 0xb6, 0x44, 0xec, 0x0b, 0x11, 0xfa, 0x98, 0x47, 0x1e, 0x8b, - 0x80, 0xf1, 0x1d, 0x30, 0x6f, 0xc8, 0x8a, 0x31, 0x1d, 0xa2, 0x09, 0x9c, 0x61, 0x32, 0xcd, 0xda, - 0x4d, 0x21, 0xf3, 0xde, 0x3c, 0x37, 0x1f, 0xbe, 0x54, 0x46, 0x4f, 0xb0, 0xbc, 0xdd, 0x45, 0xb1, - 0x27, 0x5a, 0xf8, 0x60, 0xf5, 0xc7, 0xdf, 0xcc, 0x25, 0xeb, 0xf7, 0x65, 0x70, 0xf7, 0x90, 0x24, - 0x14, 0x25, 0x74, 0x4a, 0xe5, 0x6d, 0x77, 0x41, 0xa3, 0x1c, 0x38, 0xe2, 0xba, 0xf3, 0xe3, 0x5c, - 0x6c, 0xc9, 0xcf, 0x0b, 0x84, 0x5b, 0xe7, 0xc7, 0xf9, 0x8c, 0x77, 0x5e, 0x95, 0x66, 0x7c, 0x04, - 0x56, 0x33, 0x42, 0x98, 0x9a, 0x07, 0x96, 0xd6, 0x0d, 0xd5, 0x04, 0x9a, 0xf5, 0xed, 0x27, 0x28, - 0x7b, 0x1a, 0x21, 0x8f, 0x10, 0xe6, 0xae, 0x72, 0x1a, 0x4f, 0x64, 0x19, 0x3f, 0xd5, 0xc0, 0x66, - 0x82, 0xce, 0x98, 0x5f, 0x4e, 0x59, 0xea, 0x4f, 0x20, 0x9d, 0x88, 0x3b, 0xdf, 0x72, 0xbf, 0x9a, - 0xe7, 0xe6, 0x5b, 0xb2, 0x06, 0x37, 0xa1, 0xac, 0x7f, 0x72, 0xf3, 0x83, 0x31, 0x66, 0x93, 0xe9, - 0x90, 0xcb, 0xe9, 0xb3, 0x5f, 0x5b, 0x46, 0x78, 0x48, 0x9d, 0xe1, 0x39, 0x43, 0xd4, 0x3e, 0x42, - 0x67, 0x2e, 0x5f, 0x78, 0x06, 0xa7, 0xfb, 0xb2, 0x64, 0x3b, 0x82, 0x74, 0xa2, 0xca, 0xf4, 0xf3, - 0x32, 0x68, 0xe9, 0xd5, 0x33, 0xfa, 0xa0, 0x21, 0x1b, 0xbb, 0x9c, 0x89, 0xee, 0xe6, 0x3c, 0x37, - 0xef, 0xc9, 0xbf, 0x55, 0x86, 0x2c, 0xaf, 0x2e, 0xd7, 0xc7, 0xa1, 0x01, 0x41, 0x7d, 0x82, 0x60, - 0x88, 0x32, 0xbf, 0xaf, 0xea, 0xf2, 0xf0, 0xb6, 0x39, 0x79, 0x24, 0xf0, 0x6e, 0xe7, 0x32, 0x37, - 0xd7, 0xe4, 0xba, 0x3f, 0xcf, 0xcd, 0x0d, 0x29, 0x52, 0x90, 0x59, 0xde, 0x9a, 0x5c, 0xf6, 0x35, - 0x89, 0x81, 0x9a, 0x8f, 0xff, 0x43, 0x62, 0xf0, 0x82, 0xc4, 0xa0, 0x94, 0x18, 0xa8, 0x7a, 0xfc, - 0xba, 0x02, 0xee, 0x48, 0xb4, 0x01, 0xc1, 0x3a, 0xc5, 0xe3, 0x04, 0x85, 0xbe, 0x84, 0xa8, 0x96, - 0xe9, 0xe8, 0x3a, 0xf2, 0x2d, 0x3c, 0x15, 0x30, 0x25, 0xb8, 0x7b, 0x91, 0x9b, 0xb5, 0x6a, 0x0a, - 0x5c, 0xa3, 0xb0, 0xbc, 0x16, 0xd5, 0xb0, 0x7c, 0xc8, 0x94, 0x67, 0xec, 0x53, 0x54, 0xb4, 0xd5, - 0x0d, 0x12, 0xe5, 0xe1, 0x9d, 0x22, 0xe6, 0xb6, 0x2b, 0xfa, 0x6b, 0xe9, 0x96, 0xd7, 0x9a, 0x69, - 0x38, 0xe3, 0x5b, 0x20, 0x9f, 0x01, 0xa1, 0x2f, 0x86, 0xd8, 0xca, 0xad, 0x43, 0xec, 0x81, 0x1a, - 0x62, 0x6f, 0x6a, 0x8f, 0x4b, 0x99, 0x6f, 0x79, 0xeb, 0xca, 0xa1, 0xc6, 0x58, 0x04, 0x8c, 0x02, - 0x51, 0x35, 0xab, 0x7a, 0x58, 0x6e, 0xdb, 0xc5, 0x83, 0x79, 0x6e, 0x6e, 0x5f, 0x57, 0xa9, 0x38, - 0x2c, 0xef, 0x75, 0xe5, 0xac, 0xda, 0xd6, 0xfa, 0x14, 0xd4, 0x8b, 0x07, 0xd6, 0xd8, 0x05, 0x8d, - 0x64, 0x1a, 0xa3, 0x8c, 0x47, 0xc4, 0xc9, 0xac, 0x7a, 0x95, 0xc3, 0xe8, 0x82, 0x66, 0x88, 0x12, - 0x12, 0xe3, 0x44, 0xc4, 0x97, 0x45, 0x5c, 0x77, 0xb9, 0xfe, 0xf3, 0xcb, 0x4e, 0xed, 0xe2, 0xb2, - 0x53, 0xfb, 0xeb, 0xb2, 0x53, 0x7b, 0x76, 0xd5, 0x59, 0xba, 0xb8, 0xea, 0x2c, 0xfd, 0x71, 0xd5, - 0x59, 0xfa, 0xfa, 0xb1, 0x76, 0xc5, 0x02, 0x42, 0x63, 0x42, 0xf9, 0x67, 0xd7, 0xde, 0x98, 0x38, - 0xb3, 0x7d, 0x27, 0x26, 0xe1, 0x34, 0x42, 0x54, 0x7e, 0x84, 0xed, 0x15, 0x5f, 0x61, 0xef, 0x7f, - 0xb8, 0xb7, 0xf8, 0x99, 0x34, 0xbc, 0x23, 0x46, 0xca, 0xfe, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, - 0xc4, 0x2b, 0x98, 0x08, 0xb4, 0x09, 0x00, 0x00, + 0x14, 0x6e, 0xda, 0xb2, 0x4d, 0x26, 0xe9, 0x76, 0x31, 0xa5, 0x9b, 0x96, 0x6e, 0x1c, 0x19, 0xa9, + 0xe4, 0x40, 0x6d, 0x92, 0x22, 0x21, 0x55, 0x5c, 0x70, 0x77, 0x51, 0x8b, 0x58, 0xa9, 0x72, 0xf9, + 0x21, 0x21, 0x21, 0x33, 0xb1, 0x27, 0xc9, 0x68, 0x6d, 0x8f, 0xe5, 0x99, 0x84, 0x96, 0xbf, 0x00, + 0x0e, 0x48, 0x7b, 0x44, 0x9c, 0x38, 0xf0, 0xc7, 0xec, 0xb1, 0x47, 0x4e, 0x06, 0xb5, 0x57, 0x4e, + 0x39, 0x72, 0x42, 0xf3, 0xc3, 0xf6, 0x34, 0xdb, 0xa5, 0x5a, 0x2e, 0xd1, 0xbc, 0xf7, 0xbe, 0xf7, + 0x7d, 0x99, 0x37, 0x6f, 0xde, 0x18, 0x38, 0x78, 0x18, 0x38, 0x11, 0x1e, 0x4f, 0x58, 0x10, 0x61, + 0x94, 0x30, 0xea, 0x30, 0x94, 0x84, 0x28, 0x8b, 0x71, 0xc2, 0x9c, 0x59, 0x5f, 0xb3, 0xec, 0x34, + 0x23, 0x8c, 0x18, 0x1d, 0x3c, 0x0c, 0x6c, 0x3d, 0xc1, 0xd6, 0x20, 0xb3, 0xfe, 0x4e, 0x57, 0xcb, + 0x67, 0x17, 0x29, 0xa2, 0xce, 0x0c, 0x46, 0x38, 0x84, 0x8c, 0x64, 0x92, 0x61, 0x67, 0xf7, 0x25, + 0x84, 0xf8, 0x55, 0xd1, 0x56, 0x9a, 0x11, 0x32, 0x2a, 0xac, 0xce, 0x98, 0x90, 0x71, 0x84, 0x1c, + 0x61, 0x0d, 0xa7, 0x23, 0x27, 0x9c, 0x66, 0x90, 0x61, 0x92, 0xa8, 0xb8, 0xb9, 0x18, 0x67, 0x38, + 0x46, 0x94, 0xc1, 0x38, 0x2d, 0x00, 0x7c, 0x7f, 0x01, 0xc9, 0x90, 0x23, 0xff, 0x2e, 0xdf, 0x93, + 0x5c, 0x29, 0xc0, 0x7b, 0x15, 0x80, 0xc4, 0x31, 0x66, 0x71, 0x01, 0x2a, 0x2d, 0x05, 0xdc, 0x1c, + 0x93, 0x31, 0x11, 0x4b, 0x87, 0xaf, 0xa4, 0xd7, 0xfa, 0x7b, 0x0d, 0x34, 0x8f, 0x04, 0xdf, 0x19, + 0x83, 0x0c, 0x19, 0xdb, 0xa0, 0x1e, 0x4c, 0x20, 0x4e, 0x7c, 0x1c, 0xb6, 0x6b, 0xdd, 0x5a, 0xaf, + 0xe1, 0xad, 0x09, 0xfb, 0x24, 0x34, 0x10, 0x68, 0xb2, 0x6c, 0x4a, 0x99, 0x1f, 0xa1, 0x19, 0x8a, + 0xda, 0xcb, 0xdd, 0x5a, 0xaf, 0x39, 0xe8, 0xd9, 0xff, 0x5d, 0x4f, 0xfb, 0xd3, 0x0c, 0x06, 0x7c, + 0xc3, 0xee, 0xce, 0x8b, 0xdc, 0x5c, 0x9a, 0xe7, 0xa6, 0x71, 0x01, 0xe3, 0xe8, 0xd0, 0xd2, 0xa8, + 0x2c, 0x0f, 0x08, 0xeb, 0x73, 0x6e, 0x18, 0x23, 0xb0, 0x21, 0x2c, 0x9c, 0x8c, 0xfd, 0x14, 0x65, + 0x98, 0x84, 0xed, 0x15, 0x21, 0xb5, 0x6d, 0xcb, 0x62, 0xd9, 0x45, 0xb1, 0xec, 0xc7, 0xaa, 0x98, + 0xae, 0xa5, 0xb8, 0xb7, 0x34, 0xee, 0x2a, 0xdf, 0xfa, 0xe5, 0x4f, 0xb3, 0xe6, 0xdd, 0x2f, 0xbc, + 0xa7, 0xc2, 0x69, 0x60, 0xf0, 0x60, 0x9a, 0x0c, 0x49, 0x12, 0x6a, 0x42, 0xab, 0x77, 0x09, 0xbd, + 0xab, 0x84, 0x1e, 0x4a, 0xa1, 0x45, 0x02, 0xa9, 0xb4, 0x51, 0xba, 0x95, 0x14, 0x02, 0x1b, 0x31, + 0x3c, 0xf7, 0x83, 0x88, 0x04, 0xcf, 0xfc, 0x30, 0xc3, 0x23, 0xd6, 0x7e, 0xe3, 0x35, 0xb7, 0xb4, + 0x90, 0x2f, 0x85, 0xd6, 0x63, 0x78, 0x7e, 0xc4, 0x9d, 0x8f, 0xb9, 0xcf, 0xf8, 0x16, 0xac, 0x8f, + 0x32, 0xf2, 0x03, 0x4a, 0xfc, 0x09, 0xe2, 0x07, 0xd2, 0xbe, 0x27, 0x44, 0x76, 0xc4, 0x11, 0xf1, + 0x16, 0xb1, 0x55, 0xe7, 0xcc, 0xfa, 0xf6, 0xb1, 0x40, 0xb8, 0xbb, 0x4a, 0x65, 0x53, 0xaa, 0xdc, + 0x48, 0xb7, 0xbc, 0x96, 0xb4, 0x25, 0x96, 0xd3, 0x47, 0x90, 0x21, 0xca, 0x0a, 0xfa, 0xb5, 0xd7, + 0xa5, 0xbf, 0x91, 0x6e, 0x79, 0x2d, 0x69, 0x2b, 0xfa, 0x13, 0xd0, 0x14, 0x57, 0xc7, 0xa7, 0x29, + 0x0a, 0x68, 0xbb, 0xde, 0x5d, 0xe9, 0x35, 0x07, 0x0f, 0x6c, 0x1c, 0xd0, 0xc1, 0x81, 0x7d, 0xca, + 0x23, 0x67, 0x29, 0x0a, 0xdc, 0xad, 0xaa, 0x85, 0x34, 0xb8, 0xe5, 0x81, 0xb4, 0x80, 0x50, 0xe3, + 0x10, 0xb4, 0xa6, 0xe9, 0x38, 0x83, 0x21, 0xf2, 0x53, 0xc8, 0x26, 0xed, 0x46, 0x77, 0xa5, 0xd7, + 0x70, 0x1f, 0xce, 0x73, 0xf3, 0x2d, 0x75, 0x6e, 0x5a, 0xd4, 0xf2, 0x9a, 0xca, 0x3c, 0x85, 0x6c, + 0x62, 0x40, 0xb0, 0x0d, 0xa3, 0x88, 0x7c, 0xef, 0x4f, 0xd3, 0x10, 0x32, 0xe4, 0xc3, 0x11, 0x43, + 0x99, 0x8f, 0xce, 0x53, 0x9c, 0x5d, 0xb4, 0x41, 0xb7, 0xd6, 0xab, 0xbb, 0x7b, 0xf3, 0xdc, 0xec, + 0x4a, 0xa2, 0x57, 0x42, 0xad, 0x76, 0xcd, 0xdb, 0x12, 0xd1, 0x2f, 0x45, 0xf0, 0x13, 0x1e, 0x7b, + 0x22, 0x42, 0x06, 0x05, 0xe6, 0x2d, 0x79, 0x31, 0xa6, 0x43, 0x34, 0x81, 0x33, 0x4c, 0xa6, 0x59, + 0xbb, 0x29, 0x84, 0xde, 0x9f, 0xe7, 0xe6, 0xde, 0x2b, 0x85, 0xf4, 0x04, 0x2e, 0xb7, 0xbb, 0x28, + 0xf7, 0x54, 0x03, 0x1c, 0xae, 0xfe, 0xf8, 0x9b, 0xb9, 0x64, 0xfd, 0xbe, 0x0c, 0xee, 0x1f, 0x91, + 0x84, 0xa2, 0x84, 0x4e, 0xa9, 0xbc, 0xf1, 0x2e, 0x68, 0x94, 0x43, 0x47, 0x5c, 0x79, 0x7e, 0xa4, + 0x8b, 0x6d, 0xf9, 0x45, 0x81, 0x70, 0xeb, 0xfc, 0x48, 0x9f, 0xf3, 0xee, 0xab, 0xd2, 0x8c, 0x8f, + 0xc1, 0x6a, 0x46, 0x08, 0x53, 0x33, 0xc1, 0xd2, 0x3a, 0xa2, 0x9a, 0x42, 0xb3, 0xbe, 0xfd, 0x14, + 0x65, 0xcf, 0x22, 0xe4, 0x11, 0xc2, 0xdc, 0x55, 0x4e, 0xe3, 0x89, 0x2c, 0xe3, 0xa7, 0x1a, 0xd8, + 0x4c, 0xd0, 0x39, 0xf3, 0xcb, 0x49, 0x4b, 0xfd, 0x09, 0xa4, 0x13, 0x71, 0xef, 0x5b, 0xee, 0xd7, + 0xf3, 0xdc, 0x7c, 0x47, 0x56, 0xe1, 0x36, 0x94, 0xf5, 0x4f, 0x6e, 0x7e, 0x38, 0xc6, 0x6c, 0x32, + 0x1d, 0x72, 0x39, 0x7d, 0xfe, 0x6b, 0xcb, 0x08, 0x0f, 0xa9, 0x33, 0xbc, 0x60, 0x88, 0xda, 0xc7, + 0xe8, 0xdc, 0xe5, 0x0b, 0xcf, 0xe0, 0x74, 0x5f, 0x95, 0x6c, 0xc7, 0x90, 0x4e, 0x54, 0x99, 0x7e, + 0x5e, 0x06, 0x2d, 0xbd, 0x7a, 0x46, 0x1f, 0x34, 0x64, 0x73, 0x97, 0x73, 0xd1, 0xdd, 0x9c, 0xe7, + 0xe6, 0x03, 0xf9, 0xb7, 0xca, 0x90, 0xe5, 0xd5, 0xe5, 0xfa, 0x24, 0x34, 0x20, 0xa8, 0x4f, 0x10, + 0x0c, 0x51, 0xe6, 0xf7, 0x55, 0x5d, 0xf6, 0xee, 0x9a, 0x95, 0xc7, 0x02, 0xef, 0x76, 0xae, 0x72, + 0x73, 0x4d, 0xae, 0xfb, 0xf3, 0xdc, 0xdc, 0x90, 0x22, 0x05, 0x99, 0xe5, 0xad, 0xc9, 0x65, 0x5f, + 0x93, 0x18, 0xa8, 0x19, 0xf9, 0x3f, 0x24, 0x06, 0x2f, 0x49, 0x0c, 0x4a, 0x89, 0x81, 0xaa, 0xc7, + 0xaf, 0x2b, 0xe0, 0x9e, 0x44, 0x1b, 0x10, 0xac, 0x53, 0x3c, 0x4e, 0x50, 0xe8, 0x4b, 0x88, 0x6a, + 0x99, 0x8e, 0xae, 0x23, 0xdf, 0xc3, 0x33, 0x01, 0x53, 0x82, 0xbb, 0x97, 0xb9, 0x59, 0xab, 0x26, + 0xc1, 0x0d, 0x0a, 0xcb, 0x6b, 0x51, 0x0d, 0xcb, 0x07, 0x4d, 0x79, 0xc6, 0x3e, 0x45, 0x45, 0x5b, + 0xdd, 0x22, 0x51, 0x1e, 0xde, 0x19, 0x62, 0x6e, 0xbb, 0xa2, 0xbf, 0x91, 0x6e, 0x79, 0xad, 0x99, + 0x86, 0x33, 0xbe, 0x03, 0xf2, 0x29, 0x10, 0xfa, 0x62, 0x90, 0xad, 0xdc, 0x39, 0xc8, 0x1e, 0xa9, + 0x41, 0xf6, 0xb6, 0xf6, 0xc0, 0x94, 0xf9, 0x96, 0xb7, 0xae, 0x1c, 0x6a, 0x94, 0x45, 0xc0, 0x28, + 0x10, 0x55, 0xb3, 0xaa, 0xc7, 0xe5, 0xae, 0x5d, 0x3c, 0x9a, 0xe7, 0xe6, 0xf6, 0x4d, 0x95, 0x8a, + 0xc3, 0xf2, 0xde, 0x54, 0xce, 0xaa, 0x6d, 0xad, 0xcf, 0x40, 0xbd, 0x78, 0x64, 0x8d, 0x5d, 0xd0, + 0x48, 0xa6, 0x31, 0xca, 0x78, 0x44, 0x9c, 0xcc, 0xaa, 0x57, 0x39, 0x8c, 0x2e, 0x68, 0x86, 0x28, + 0x21, 0x31, 0x4e, 0x44, 0x7c, 0x59, 0xc4, 0x75, 0x97, 0xeb, 0xbf, 0xb8, 0xea, 0xd4, 0x2e, 0xaf, + 0x3a, 0xb5, 0xbf, 0xae, 0x3a, 0xb5, 0xe7, 0xd7, 0x9d, 0xa5, 0xcb, 0xeb, 0xce, 0xd2, 0x1f, 0xd7, + 0x9d, 0xa5, 0x6f, 0x9e, 0x68, 0x57, 0x2c, 0x20, 0x34, 0x26, 0x94, 0x7f, 0x7a, 0xed, 0x8f, 0x89, + 0x33, 0x3b, 0x70, 0x62, 0x12, 0x4e, 0x23, 0x44, 0xe5, 0x87, 0xd8, 0x7e, 0xf1, 0x25, 0xf6, 0xc1, + 0x47, 0xfb, 0x8b, 0x9f, 0x4a, 0xc3, 0x7b, 0x62, 0xa4, 0x1c, 0xfc, 0x1b, 0x00, 0x00, 0xff, 0xff, + 0x31, 0x4f, 0x07, 0x47, 0xb8, 0x09, 0x00, 0x00, } func (m *ClientState) Marshal() (dAtA []byte, err error) { diff --git a/modules/light-clients/07-tendermint/types/upgrade.go b/modules/light-clients/07-tendermint/types/upgrade.go index 5e23c8d9036..e5c3157657b 100644 --- a/modules/light-clients/07-tendermint/types/upgrade.go +++ b/modules/light-clients/07-tendermint/types/upgrade.go @@ -20,10 +20,10 @@ import ( // - the upgradedClient is not a Tendermint ClientState // - the lastest 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 +// - 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 sdk.KVStore, upgradedClient exported.ClientState, upgradedConsState exported.ConsensusState, diff --git a/modules/light-clients/07-tendermint/types/upgrade_test.go b/modules/light-clients/07-tendermint/types/upgrade_test.go index 175ce7fc358..6ad81df3520 100644 --- a/modules/light-clients/07-tendermint/types/upgrade_test.go +++ b/modules/light-clients/07-tendermint/types/upgrade_test.go @@ -10,9 +10,7 @@ import ( ibctesting "github.com/cosmos/ibc-go/v3/testing" ) -var ( - newChainId = "newChainId-1" -) +var newChainId = "newChainId-1" func (suite *TendermintTestSuite) TestVerifyUpgrade() { var ( diff --git a/modules/light-clients/09-localhost/types/client_state_test.go b/modules/light-clients/09-localhost/types/client_state_test.go index a54cc8efe9a..04b57fcbbf0 100644 --- a/modules/light-clients/09-localhost/types/client_state_test.go +++ b/modules/light-clients/09-localhost/types/client_state_test.go @@ -150,7 +150,6 @@ func (suite *LocalhostTestSuite) TestVerifyClientState() { } }) } - } func (suite *LocalhostTestSuite) TestVerifyClientConsensusState() { @@ -291,7 +290,6 @@ func (suite *LocalhostTestSuite) TestVerifyChannelState() { clientState: types.NewClientState("chainID", clientHeight), malleate: func() { suite.store.Set(host.ChannelKey(testPortID, testChannelID), []byte("channel")) - }, channel: ch1, expPass: false, @@ -303,7 +301,6 @@ func (suite *LocalhostTestSuite) TestVerifyChannelState() { bz, err := suite.cdc.Marshal(&ch2) suite.Require().NoError(err) suite.store.Set(host.ChannelKey(testPortID, testChannelID), bz) - }, channel: ch1, expPass: false, diff --git a/modules/light-clients/09-localhost/types/localhost_test.go b/modules/light-clients/09-localhost/types/localhost_test.go index ee8507c4efc..cb80d5ba424 100644 --- a/modules/light-clients/09-localhost/types/localhost_test.go +++ b/modules/light-clients/09-localhost/types/localhost_test.go @@ -17,9 +17,7 @@ const ( height = 4 ) -var ( - clientHeight = clienttypes.NewHeight(0, 10) -) +var clientHeight = clienttypes.NewHeight(0, 10) type LocalhostTestSuite struct { suite.Suite diff --git a/proto/ibc/applications/interchain_accounts/controller/v1/query.proto b/proto/ibc/applications/interchain_accounts/controller/v1/query.proto index d3960e9d14c..0a2364cb157 100644 --- a/proto/ibc/applications/interchain_accounts/controller/v1/query.proto +++ b/proto/ibc/applications/interchain_accounts/controller/v1/query.proto @@ -5,16 +5,34 @@ package ibc.applications.interchain_accounts.controller.v1; option go_package = "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/controller/types"; import "ibc/applications/interchain_accounts/controller/v1/controller.proto"; +import "gogoproto/gogo.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 [(gogoproto.moretags) = "yaml:\"connection_id\""]; +} + +// 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 {} diff --git a/proto/ibc/applications/transfer/v1/query.proto b/proto/ibc/applications/transfer/v1/query.proto index 8491c52139b..5298338c10c 100644 --- a/proto/ibc/applications/transfer/v1/query.proto +++ b/proto/ibc/applications/transfer/v1/query.proto @@ -30,6 +30,11 @@ service Query { 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"; + } } // QueryDenomTraceRequest is the request type for the Query/DenomTrace RPC @@ -84,3 +89,17 @@ 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; +} \ No newline at end of file diff --git a/proto/ibc/applications/transfer/v1/tx.proto b/proto/ibc/applications/transfer/v1/tx.proto index 8f1392b0cf3..81a733fc530 100644 --- a/proto/ibc/applications/transfer/v1/tx.proto +++ b/proto/ibc/applications/transfer/v1/tx.proto @@ -38,7 +38,12 @@ message MsgTransfer { // Timeout timestamp in absolute nanoseconds since unix epoch. // The timeout is disabled when set to 0. uint64 timeout_timestamp = 7 [(gogoproto.moretags) = "yaml:\"timeout_timestamp\""]; + // optional memo + string memo = 8; } // MsgTransferResponse defines the Msg/Transfer response type. -message MsgTransferResponse {} +message MsgTransferResponse { + // sequence number of the transfer packet sent + uint64 sequence = 1; +} diff --git a/proto/ibc/applications/transfer/v2/packet.proto b/proto/ibc/applications/transfer/v2/packet.proto index 850320df340..d8382f6469a 100644 --- a/proto/ibc/applications/transfer/v2/packet.proto +++ b/proto/ibc/applications/transfer/v2/packet.proto @@ -16,4 +16,6 @@ message FungibleTokenPacketData { string sender = 3; // the recipient address on the destination chain string receiver = 4; + // optional memo + string memo = 5; } diff --git a/proto/ibc/core/channel/v1/tx.proto b/proto/ibc/core/channel/v1/tx.proto index 15714173a1a..d34b00e9124 100644 --- a/proto/ibc/core/channel/v1/tx.proto +++ b/proto/ibc/core/channel/v1/tx.proto @@ -68,6 +68,7 @@ message MsgChannelOpenInit { // MsgChannelOpenInitResponse defines the Msg/ChannelOpenInit response type. message MsgChannelOpenInitResponse { string channel_id = 1 [(gogoproto.moretags) = "yaml:\"channel_id\""]; + string version = 2; } // MsgChannelOpenInit defines a msg sent by a Relayer to try to open a channel @@ -91,7 +92,9 @@ message MsgChannelOpenTry { } // MsgChannelOpenTryResponse defines the Msg/ChannelOpenTry response type. -message MsgChannelOpenTryResponse {} +message MsgChannelOpenTryResponse { + string version = 1; +} // MsgChannelOpenAck defines a msg sent by a Relayer to Chain A to acknowledge // the change of channel state to TRYOPEN on Chain B. diff --git a/proto/ibc/core/client/v1/query.proto b/proto/ibc/core/client/v1/query.proto index 91a906fe54b..33a4191cd06 100644 --- a/proto/ibc/core/client/v1/query.proto +++ b/proto/ibc/core/client/v1/query.proto @@ -36,6 +36,11 @@ service Query { 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}"; @@ -137,6 +142,24 @@ message QueryConsensusStatesResponse { 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 { diff --git a/proto/ibc/lightclients/dymint/dymint.proto b/proto/ibc/lightclients/dymint/dymint.proto old mode 100644 new mode 100755 index c0f5fc9f727..1702083b755 --- a/proto/ibc/lightclients/dymint/dymint.proto +++ b/proto/ibc/lightclients/dymint/dymint.proto @@ -1,6 +1,6 @@ syntax = "proto3"; -package ibc.lightclients.dymint.v1; +package ibc.lightclients.dymint; option go_package = "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types"; @@ -51,13 +51,6 @@ message ClientState { // the default upgrade module, upgrade_path should be []string{"upgrade", // "upgradedIBCState"}` repeated string upgrade_path = 9 [(gogoproto.moretags) = "yaml:\"upgrade_path\""]; - - // This flag, when set to true, will allow governance to recover a client - // which has expired - bool allow_update_after_expiry = 10 [(gogoproto.moretags) = "yaml:\"allow_update_after_expiry\""]; - // This flag, when set to true, will allow governance to unfreeze a client - // whose chain has experienced a misbehaviour event - bool allow_update_after_misbehaviour = 11 [(gogoproto.moretags) = "yaml:\"allow_update_after_misbehaviour\""]; } // ConsensusState defines the consensus state from Dymint. diff --git a/proto/ibc/lightclients/tendermint/v1/tendermint.proto b/proto/ibc/lightclients/tendermint/v1/tendermint.proto index 322574fa5bf..2815fe6ad2a 100644 --- a/proto/ibc/lightclients/tendermint/v1/tendermint.proto +++ b/proto/ibc/lightclients/tendermint/v1/tendermint.proto @@ -52,12 +52,11 @@ message ClientState { // "upgradedIBCState"}` repeated string upgrade_path = 9 [(gogoproto.moretags) = "yaml:\"upgrade_path\""]; - // This flag, when set to true, will allow governance to recover a client - // which has expired - bool allow_update_after_expiry = 10 [(gogoproto.moretags) = "yaml:\"allow_update_after_expiry\""]; - // This flag, when set to true, will allow governance to unfreeze a client - // whose chain has experienced a misbehaviour event - bool allow_update_after_misbehaviour = 11 [(gogoproto.moretags) = "yaml:\"allow_update_after_misbehaviour\""]; + // allow_update_after_expiry is deprecated + bool allow_update_after_expiry = 10 [deprecated = true, (gogoproto.moretags) = "yaml:\"allow_update_after_expiry\""]; + // allow_update_after_misbehaviour is deprecated + bool allow_update_after_misbehaviour = 11 + [deprecated = true, (gogoproto.moretags) = "yaml:\"allow_update_after_misbehaviour\""]; } // ConsensusState defines the consensus state from Tendermint. diff --git a/testing/chain.go b/testing/chain.go index 4446b449f49..3db1a27d0bf 100644 --- a/testing/chain.go +++ b/testing/chain.go @@ -160,14 +160,18 @@ func NewTestChainWithValSet(t *testing.T, coord *Coordinator, chainID string, va return chain } -// NewTestChain initializes a new test chain with a default of 4 validators +// NewTestChain initializes a new test chain with a default of 4 validators or 1 for dymint // Use this function if the tests do not need custom control over the validator set func NewTestChain(t *testing.T, coord *Coordinator, chainID string, chainConsensusType string) *TestChain { + // currently dymint can have only one sequencer + validatorsPerChain := 4 + if chainConsensusType == exported.Dymint { + validatorsPerChain = 1 + } // generate validators private/public key var ( - validatorsPerChain = 4 - validators []*tmtypes.Validator - signersByAddress = make(map[string]tmtypes.PrivValidator, validatorsPerChain) + validators []*tmtypes.Validator + signersByAddress = make(map[string]tmtypes.PrivValidator, validatorsPerChain) ) for i := 0; i < validatorsPerChain; i++ { @@ -433,6 +437,12 @@ func (chain *TestChain) GetChannelCapability(portID, channelID string) *capabili return cap } +// 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) +} + // ConstructUpdateClientHeader will construct a valid 01-dymint Header to update the // light client on the source chain. func (chain *TestChain) ConstructUpdateClientHeader(counterparty *TestChain, clientID string) (exported.Header, error) { diff --git a/testing/chain_dymint.go b/testing/chain_dymint.go index 716e4fdaa0a..4925330b831 100644 --- a/testing/chain_dymint.go +++ b/testing/chain_dymint.go @@ -17,13 +17,12 @@ import ( commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" "github.com/cosmos/ibc-go/v3/modules/core/exported" ibcdmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" + "github.com/cosmos/ibc-go/v3/testing/mock" ) type DymintConfig struct { - TrustingPeriod time.Duration - MaxClockDrift time.Duration - AllowUpdateAfterExpiry bool - AllowUpdateAfterMisbehaviour bool + TrustingPeriod time.Duration + MaxClockDrift time.Duration } func (tmcfg *DymintConfig) GetClientType() string { @@ -70,10 +69,8 @@ func (chain *TestChainDymint) GetSelfClientType() string { func (chain *TestChainDymint) NewConfig() ClientConfig { return &DymintConfig{ - TrustingPeriod: TrustingPeriod, - MaxClockDrift: MaxClockDrift, - AllowUpdateAfterExpiry: false, - AllowUpdateAfterMisbehaviour: false, + TrustingPeriod: TrustingPeriod, + MaxClockDrift: MaxClockDrift, } } @@ -189,6 +186,17 @@ func (chain *TestChainDymint) CreateDMClientHeader(chainID string, blockHeight i Commit: commit.ToProto(), } + // only one sequencer can sign + pv, ok := signers[0].(mock.PV) + require.True(chain.TC.T, ok) + headerBytes, err := tmHeader.ToProto().Marshal() + require.NoError(chain.TC.T, err) + signedBytes, err := pv.PrivKey.Sign(headerBytes) + require.NoError(chain.TC.T, err) + + // Dymint check the header bytes signatures + signedHeader.Commit.Signatures[0].Signature = signedBytes + if tmValSet != nil { valSet, err = tmValSet.ToProto() require.NoError(chain.TC.T, err) @@ -227,7 +235,7 @@ func (chain *TestChainDymint) ClientConfigToState(clientConfig ClientConfig) exp height := chain.LastHeader.GetHeight().(clienttypes.Height) clientState := ibcdmtypes.NewClientState( chain.TC.ChainID, tmConfig.TrustingPeriod, tmConfig.MaxClockDrift, - height, commitmenttypes.GetSDKSpecs(), UpgradePath, tmConfig.AllowUpdateAfterExpiry, tmConfig.AllowUpdateAfterMisbehaviour, + height, commitmenttypes.GetSDKSpecs(), UpgradePath, ) return clientState } diff --git a/testing/chain_tendermint.go b/testing/chain_tendermint.go index 5cbc6a72adf..a5147a9af9a 100644 --- a/testing/chain_tendermint.go +++ b/testing/chain_tendermint.go @@ -20,11 +20,6 @@ import ( ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" ) -var ( - // Default params variables used to create a TM client - DefaultTrustLevel ibctmtypes.Fraction = ibctmtypes.DefaultTrustLevel -) - type TendermintConfig struct { TrustLevel ibctmtypes.Fraction TrustingPeriod time.Duration diff --git a/testing/events.go b/testing/events.go index 7828b42619f..8beaa0a2beb 100644 --- a/testing/events.go +++ b/testing/events.go @@ -64,7 +64,6 @@ func ParsePacketFromEvents(events sdk.Events) (channeltypes.Packet, error) { if ev.Type == channeltypes.EventTypeSendPacket { packet := channeltypes.Packet{} for _, attr := range ev.Attributes { - switch string(attr.Key) { case channeltypes.AttributeKeyData: packet.Data = attr.Value diff --git a/testing/mock/ibc_module.go b/testing/mock/ibc_module.go index e58f6ae7156..215cb6e4f31 100644 --- a/testing/mock/ibc_module.go +++ b/testing/mock/ibc_module.go @@ -35,7 +35,6 @@ func (im IBCModule) OnChanOpenInit( ) error { if im.IBCApp.OnChanOpenInit != nil { return im.IBCApp.OnChanOpenInit(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, version) - } // Claim channel capability passed back by IBC module diff --git a/testing/mock/ibc_module_test.go b/testing/mock/ibc_module_test.go index d3efe9f142c..b36d58d2004 100644 --- a/testing/mock/ibc_module_test.go +++ b/testing/mock/ibc_module_test.go @@ -2,7 +2,7 @@ package mock_test import ( "testing" - + "github.com/stretchr/testify/require" clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" diff --git a/testing/simapp/ante_handler.go b/testing/simapp/ante_handler.go index 04ffad13e2a..5d32e9199bb 100644 --- a/testing/simapp/ante_handler.go +++ b/testing/simapp/ante_handler.go @@ -28,7 +28,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for ante builder") } - var sigGasConsumer = options.SigGasConsumer + sigGasConsumer := options.SigGasConsumer if sigGasConsumer == nil { sigGasConsumer = ante.DefaultSigVerificationGasConsumer } diff --git a/testing/simapp/app.go b/testing/simapp/app.go index 38d340000ae..0562740ecb5 100644 --- a/testing/simapp/app.go +++ b/testing/simapp/app.go @@ -421,7 +421,7 @@ func NewSimAppWithConsensusType( // NOTE: we may consider parsing `appOpts` inside module constructors. For the moment // we prefer to be more strict in what arguments the modules expect. - var skipGenesisInvariants = cast.ToBool(appOpts.Get(crisis.FlagSkipGenesisInvariants)) + 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. diff --git a/testing/simapp/export.go b/testing/simapp/export.go index fba37920697..ee94a432016 100644 --- a/testing/simapp/export.go +++ b/testing/simapp/export.go @@ -45,7 +45,7 @@ func (app *SimApp) ExportAppStateAndValidators( // 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 +// in favour of export at a block height func (app *SimApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs []string) { applyAllowedAddrs := false diff --git a/testing/simapp/params/amino.go b/testing/simapp/params/amino.go index 440c29f817e..0d43dd1eb8f 100644 --- a/testing/simapp/params/amino.go +++ b/testing/simapp/params/amino.go @@ -1,3 +1,4 @@ +//go:build test_amino // +build test_amino package params diff --git a/testing/simapp/params/proto.go b/testing/simapp/params/proto.go index 04aa524b900..a752d107907 100644 --- a/testing/simapp/params/proto.go +++ b/testing/simapp/params/proto.go @@ -1,3 +1,4 @@ +//go:build !test_amino // +build !test_amino package params diff --git a/testing/simapp/sim_test.go b/testing/simapp/sim_test.go index bbc8f4a8c0c..12c96755aca 100644 --- a/testing/simapp/sim_test.go +++ b/testing/simapp/sim_test.go @@ -163,11 +163,13 @@ func TestAppImportExport(t *testing.T) { storeKeysPrefixes := []StoreKeysPrefixes{ {app.keys[authtypes.StoreKey], newApp.keys[authtypes.StoreKey], [][]byte{}}, - {app.keys[stakingtypes.StoreKey], newApp.keys[stakingtypes.StoreKey], + { + app.keys[stakingtypes.StoreKey], newApp.keys[stakingtypes.StoreKey], [][]byte{ stakingtypes.UnbondingQueueKey, stakingtypes.RedelegationQueueKey, stakingtypes.ValidatorQueueKey, stakingtypes.HistoricalInfoKey, - }}, // ordering may change but it doesn't matter + }, + }, // ordering may change but it doesn't matter {app.keys[slashingtypes.StoreKey], newApp.keys[slashingtypes.StoreKey], [][]byte{}}, {app.keys[minttypes.StoreKey], newApp.keys[minttypes.StoreKey], [][]byte{}}, {app.keys[distrtypes.StoreKey], newApp.keys[distrtypes.StoreKey], [][]byte{}}, diff --git a/testing/simapp/simd/cmd/genaccounts_test.go b/testing/simapp/simd/cmd/genaccounts_test.go index 0cdf365bf93..c7c2f92682c 100644 --- a/testing/simapp/simd/cmd/genaccounts_test.go +++ b/testing/simapp/simd/cmd/genaccounts_test.go @@ -73,7 +73,8 @@ func TestAddGenesisAccountCmd(t *testing.T) { cmd.SetArgs([]string{ tc.addr, tc.denom, - fmt.Sprintf("--%s=home", flags.FlagHome)}) + fmt.Sprintf("--%s=home", flags.FlagHome), + }) if tc.expectErr { require.Error(t, cmd.ExecuteContext(ctx)) diff --git a/testing/simapp/simd/cmd/root.go b/testing/simapp/simd/cmd/root.go index e690093709e..e130cb16a32 100644 --- a/testing/simapp/simd/cmd/root.go +++ b/testing/simapp/simd/cmd/root.go @@ -279,8 +279,8 @@ func (a appCreator) newApp(logger log.Logger, db dbm.DB, traceStore io.Writer, a // and exports state. func (a appCreator) appExport( logger log.Logger, db dbm.DB, traceStore io.Writer, height int64, forZeroHeight bool, jailAllowedAddrs []string, - appOpts servertypes.AppOptions) (servertypes.ExportedApp, error) { - + appOpts servertypes.AppOptions, +) (servertypes.ExportedApp, error) { var simApp *simapp.SimApp homePath, ok := appOpts.Get(flags.FlagHome).(string) if !ok || homePath == "" { diff --git a/testing/simapp/simd/cmd/testnet.go b/testing/simapp/simd/cmd/testnet.go index 019f534183b..657393fa915 100644 --- a/testing/simapp/simd/cmd/testnet.go +++ b/testing/simapp/simd/cmd/testnet.go @@ -93,7 +93,7 @@ Example: return cmd } -const nodeDirPerm = 0755 +const nodeDirPerm = 0o755 // Initialize the testnet func InitTestnet( @@ -112,7 +112,6 @@ func InitTestnet( algoStr string, numValidators int, ) error { - if chainID == "" { chainID = "chain-" + tmrand.NewRand().Str(6) } @@ -269,7 +268,6 @@ func initGenFiles( genAccounts []authtypes.GenesisAccount, genBalances []banktypes.Balance, genFiles []string, numValidators int, ) error { - appGenState := mbm.DefaultGenesis(clientCtx.JSONCodec) // set the accounts in the genesis state @@ -316,7 +314,6 @@ func collectGenFiles( nodeIDs []string, valPubKeys []cryptotypes.PubKey, numValidators int, outputDir, nodeDirPrefix, nodeDaemonHome string, genBalIterator banktypes.GenesisBalancesIterator, ) error { - var appState json.RawMessage genTime := tmtime.Now() @@ -385,12 +382,12 @@ func writeFile(name string, dir string, contents []byte) error { writePath := filepath.Join(dir) file := filepath.Join(writePath, name) - err := tmos.EnsureDir(writePath, 0755) + err := tmos.EnsureDir(writePath, 0o755) if err != nil { return err } - err = tmos.WriteFile(file, contents, 0644) + err = tmos.WriteFile(file, contents, 0o644) if err != nil { return err } diff --git a/testing/simapp/state.go b/testing/simapp/state.go index 86f20e2b115..c0a9e2ea1ec 100644 --- a/testing/simapp/state.go +++ b/testing/simapp/state.go @@ -28,7 +28,6 @@ import ( func AppStateFn(cdc codec.JSONCodec, simManager *module.SimulationManager) simtypes.AppStateFn { return func(r *rand.Rand, accs []simtypes.Account, config simtypes.Config, ) (appState json.RawMessage, simAccs []simtypes.Account, chainID string, genesisTimestamp time.Time) { - if FlagGenesisTimeValue == 0 { genesisTimestamp = simtypes.RandTimestamp(r) } else { diff --git a/testing/simapp/test_helpers.go b/testing/simapp/test_helpers.go index b1c67d5fcd7..cf7b8d5ab41 100644 --- a/testing/simapp/test_helpers.go +++ b/testing/simapp/test_helpers.go @@ -239,7 +239,6 @@ func SignAndDeliver( t *testing.T, txCfg client.TxConfig, app *bam.BaseApp, header tmproto.Header, msgs []sdk.Msg, chainID string, accNums, accSeqs []uint64, expSimPass, expPass bool, priv ...cryptotypes.PrivKey, ) (sdk.GasInfo, *sdk.Result, error) { - tx, err := helpers.GenTx( txCfg, msgs, diff --git a/testing/simapp/utils.go b/testing/simapp/utils.go index 9abfc060b94..51788f5a322 100644 --- a/testing/simapp/utils.go +++ b/testing/simapp/utils.go @@ -84,7 +84,7 @@ func CheckExportSimulation( return err } - if err := ioutil.WriteFile(config.ExportStatePath, []byte(exported.AppState), 0600); err != nil { + if err := ioutil.WriteFile(config.ExportStatePath, []byte(exported.AppState), 0o600); err != nil { return err } } @@ -96,7 +96,7 @@ func CheckExportSimulation( return err } - if err := ioutil.WriteFile(config.ExportParamsPath, paramsBz, 0600); err != nil { + if err := ioutil.WriteFile(config.ExportParamsPath, paramsBz, 0o600); err != nil { return err } } diff --git a/testing/values.go b/testing/values.go index 477859cfb8c..c310a2b779e 100644 --- a/testing/values.go +++ b/testing/values.go @@ -1,6 +1,6 @@ /* - This file contains the variables, constants, and default values - used in the testing package and commonly defined in tests. +This file contains the variables, constants, and default values +used in the testing package and commonly defined in tests. */ package ibctesting @@ -12,6 +12,7 @@ import ( ibctransfertypes "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" connectiontypes "github.com/cosmos/ibc-go/v3/modules/core/03-connection/types" commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" + ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" "github.com/cosmos/ibc-go/v3/testing/mock" ) @@ -44,7 +45,10 @@ var ( DefaultOpenInitVersion *connectiontypes.Version // Default params variables used to create a TM client - TestCoin = sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)) + DefaultTrustLevel ibctmtypes.Fraction = ibctmtypes.DefaultTrustLevel + + TestAccAddress = "cosmos17dtl0mjt3t77kpuhg2edqzjpszulwhgzuj9ljs" + TestCoin = sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)) UpgradePath = []string{"upgrade", "upgradedIBCState"} From 1870174ab6daaddecb9018bf05fdae0fbf8e19db Mon Sep 17 00:00:00 2001 From: Michael Tsitrin <114929630+mtsitrin@users.noreply.github.com> Date: Thu, 5 Jan 2023 15:43:15 +0200 Subject: [PATCH 129/140] added backward compatible interface (#27) --- modules/core/keeper/keeper.go | 15 ++++++++++++++- modules/core/keeper/keeper_test.go | 2 +- testing/simapp/app.go | 2 +- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/modules/core/keeper/keeper.go b/modules/core/keeper/keeper.go index 0a0ea80998a..c4fe8959f67 100644 --- a/modules/core/keeper/keeper.go +++ b/modules/core/keeper/keeper.go @@ -9,6 +9,8 @@ import ( capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + ibcdmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" + clientkeeper "github.com/cosmos/ibc-go/v3/modules/core/02-client/keeper" clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" connectionkeeper "github.com/cosmos/ibc-go/v3/modules/core/03-connection/keeper" @@ -36,8 +38,19 @@ type Keeper struct { Router *porttypes.Router } -// NewKeeper creates a new ibc Keeper +//IBCKeeper initialized in wasm code (in wasmd@v0.28.0/x/wasm/keeper/test_common.go:354) +//We need to maintain backward comptabile interface func NewKeeper( + cdc codec.BinaryCodec, key sdk.StoreKey, paramSpace paramtypes.Subspace, + stakingKeeper clienttypes.StakingKeeper, upgradeKeeper clienttypes.UpgradeKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, +) *Keeper { + //Using dymint and nil as default. we can panic instead + return NewKeeperWithSelfClient(cdc, key, paramSpace, stakingKeeper, upgradeKeeper, scopedKeeper, ibcdmtypes.NewSelfClient(), nil) +} + +// NewKeeper creates a new ibc Keeper +func NewKeeperWithSelfClient( cdc codec.BinaryCodec, key sdk.StoreKey, paramSpace paramtypes.Subspace, stakingKeeper clienttypes.StakingKeeper, upgradeKeeper clienttypes.UpgradeKeeper, scopedKeeper capabilitykeeper.ScopedKeeper, selfClient exported.SelfClient, clientHooks exported.ClientHooks, diff --git a/modules/core/keeper/keeper_test.go b/modules/core/keeper/keeper_test.go index 912f972d6a4..4afa0dcad4b 100644 --- a/modules/core/keeper/keeper_test.go +++ b/modules/core/keeper/keeper_test.go @@ -64,7 +64,7 @@ func (suite *KeeperTestSuite) TestNewKeeper() { upgradeKeeper clienttypes.UpgradeKeeper scopedKeeper capabilitykeeper.ScopedKeeper newIBCKeeper = func() { - ibckeeper.NewKeeper( + ibckeeper.NewKeeperWithSelfClient( suite.chainA.GetSimApp().AppCodec(), suite.chainA.GetSimApp().GetKey(ibchost.StoreKey), suite.chainA.GetSimApp().GetSubspace(ibchost.ModuleName), diff --git a/testing/simapp/app.go b/testing/simapp/app.go index 0562740ecb5..748aa66280b 100644 --- a/testing/simapp/app.go +++ b/testing/simapp/app.go @@ -347,7 +347,7 @@ func NewSimAppWithConsensusType( panic(fmt.Sprintf("client type %s is not supported", chainConsensusType)) } // Create IBC Keeper - app.IBCKeeper = ibckeeper.NewKeeper( + app.IBCKeeper = ibckeeper.NewKeeperWithSelfClient( appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, selfClient, nil, ) From e315bbffc55fb7321adcb59834cabc737562994c Mon Sep 17 00:00:00 2001 From: liorzilp Date: Sun, 12 Mar 2023 11:06:13 +0200 Subject: [PATCH 130/140] feat: message interceptor (#29) * remove client hooks and add message interceptor * remove broken link --------- Co-authored-by: Lior Zilpa --- docs/ibc/integration.md | 4 +- modules/core/02-client/keeper/client.go | 24 -------- modules/core/02-client/keeper/keeper.go | 5 +- modules/core/exported/client.go | 32 ---------- modules/core/ibc_msg_interceptor.go | 77 +++++++++++++++++++++++++ modules/core/keeper/keeper.go | 10 ++-- modules/core/keeper/keeper_test.go | 1 - modules/core/module.go | 22 +++++-- testing/simapp/app.go | 2 +- 9 files changed, 103 insertions(+), 74 deletions(-) create mode 100644 modules/core/ibc_msg_interceptor.go diff --git a/docs/ibc/integration.md b/docs/ibc/integration.md index 3dd8b0a74a6..930d8bfd1d1 100644 --- a/docs/ibc/integration.md +++ b/docs/ibc/integration.md @@ -177,8 +177,8 @@ connection handhake. 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](https://github.com/cosmos/ibc/blob/master/spec/client/ics-009-loopback-client) to connect two +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 diff --git a/modules/core/02-client/keeper/client.go b/modules/core/02-client/keeper/client.go index c1622237454..d8085ac719f 100644 --- a/modules/core/02-client/keeper/client.go +++ b/modules/core/02-client/keeper/client.go @@ -17,12 +17,6 @@ import ( func (k Keeper) CreateClient( ctx sdk.Context, clientState exported.ClientState, consensusState exported.ConsensusState, ) (string, error) { - if k.clientHooks != nil { - err := k.clientHooks.OnCreateClient(ctx, clientState, consensusState) - if err != nil { - return "", err - } - } params := k.GetParams(ctx) if !params.IsAllowedClient(clientState.ClientType()) { return "", sdkerrors.Wrapf( @@ -64,12 +58,6 @@ func (k Keeper) CreateClient( // UpdateClient updates the consensus state and the state root from a provided header. func (k Keeper) UpdateClient(ctx sdk.Context, clientID string, header exported.Header) error { - if k.clientHooks != nil { - err := k.clientHooks.OnUpdateClient(ctx, clientID, header) - if err != nil { - return err - } - } clientState, found := k.GetClientState(ctx, clientID) if !found { return sdkerrors.Wrapf(types.ErrClientNotFound, "cannot update client with ID %s", clientID) @@ -159,12 +147,6 @@ func (k Keeper) UpdateClient(ctx sdk.Context, clientID string, header exported.H // by the old client at the specified upgrade height func (k Keeper) UpgradeClient(ctx sdk.Context, clientID string, upgradedClient exported.ClientState, upgradedConsState exported.ConsensusState, proofUpgradeClient, proofUpgradeConsState []byte) error { - if k.clientHooks != nil { - err := k.clientHooks.OnUpgradeClient(ctx, clientID, upgradedClient, upgradedConsState, proofUpgradeClient, proofUpgradeConsState) - if err != nil { - return err - } - } clientState, found := k.GetClientState(ctx, clientID) if !found { return sdkerrors.Wrapf(types.ErrClientNotFound, "cannot update client with ID %s", clientID) @@ -207,12 +189,6 @@ func (k Keeper) UpgradeClient(ctx sdk.Context, clientID string, upgradedClient e // CheckMisbehaviourAndUpdateState checks for client misbehaviour and freezes the // client if so. func (k Keeper) CheckMisbehaviourAndUpdateState(ctx sdk.Context, misbehaviour exported.Misbehaviour) error { - if k.clientHooks != nil { - err := k.clientHooks.OnCheckMisbehaviourAndUpdateState(ctx, misbehaviour) - if err != nil { - return err - } - } clientState, found := k.GetClientState(ctx, misbehaviour.GetClientID()) if !found { return sdkerrors.Wrapf(types.ErrClientNotFound, "cannot check misbehaviour for client with ID %s", misbehaviour.GetClientID()) diff --git a/modules/core/02-client/keeper/keeper.go b/modules/core/02-client/keeper/keeper.go index 809a5d012f8..8af3326c9a3 100644 --- a/modules/core/02-client/keeper/keeper.go +++ b/modules/core/02-client/keeper/keeper.go @@ -26,7 +26,6 @@ type Keeper struct { stakingKeeper types.StakingKeeper upgradeKeeper types.UpgradeKeeper selfClient exported.SelfClient - clientHooks exported.ClientHooks } // NewKeeper creates a new NewKeeper instance @@ -36,8 +35,7 @@ func NewKeeper( paramSpace paramtypes.Subspace, sk types.StakingKeeper, uk types.UpgradeKeeper, - selfClient exported.SelfClient, - clientHooks exported.ClientHooks) Keeper { + selfClient exported.SelfClient) Keeper { // set KeyTable if it has not already been set if !paramSpace.HasKeyTable() { paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable()) @@ -50,7 +48,6 @@ func NewKeeper( stakingKeeper: sk, upgradeKeeper: uk, selfClient: selfClient, - clientHooks: clientHooks, } } diff --git a/modules/core/exported/client.go b/modules/core/exported/client.go index 7b37d90c5c3..3d30391136f 100644 --- a/modules/core/exported/client.go +++ b/modules/core/exported/client.go @@ -261,38 +261,6 @@ type SelfClient interface { ) (ConsensusState, error) } -// ClientHooks defines an interface that implements callbacks -// for the client module methods as specified in ICS-02. -// First the callback is called and if no error, proceed to -// the method functionality -type ClientHooks interface { - OnCreateClient( - ctx sdk.Context, - clientState ClientState, - consensusState ConsensusState, - ) error - - OnUpdateClient( - ctx sdk.Context, - clientID string, - header Header, - ) error - - OnUpgradeClient( - ctx sdk.Context, - clientID string, - upgradedClient ClientState, - upgradedConsState ConsensusState, - proofUpgradeClient, - proofUpgradeConsState []byte, - ) error - - OnCheckMisbehaviourAndUpdateState( - ctx sdk.Context, - misbehaviour Misbehaviour, - ) error -} - // String returns the string representation of a client status. func (s Status) String() string { return string(s) diff --git a/modules/core/ibc_msg_interceptor.go b/modules/core/ibc_msg_interceptor.go new file mode 100644 index 00000000000..779171b408e --- /dev/null +++ b/modules/core/ibc_msg_interceptor.go @@ -0,0 +1,77 @@ +package ibc + +import ( + "context" + + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v3/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" +) + +type IBCMsgI interface { + ///////////////////////////////////////////////////////////////////////////// + // Keeper + ///////////////////////////////////////////////////////////////////////////// + + // CreateClient defines a rpc handler method for MsgCreateClient. + CreateClient(goCtx context.Context, msg *clienttypes.MsgCreateClient) (*clienttypes.MsgCreateClientResponse, error) + + // UpdateClient defines a rpc handler method for MsgUpdateClient. + UpdateClient(goCtx context.Context, msg *clienttypes.MsgUpdateClient) (*clienttypes.MsgUpdateClientResponse, error) + + // UpgradeClient defines a rpc handler method for MsgUpgradeClient. + UpgradeClient(goCtx context.Context, msg *clienttypes.MsgUpgradeClient) (*clienttypes.MsgUpgradeClientResponse, error) + + // SubmitMisbehaviour defines a rpc handler method for MsgSubmitMisbehaviour. + SubmitMisbehaviour(goCtx context.Context, msg *clienttypes.MsgSubmitMisbehaviour) (*clienttypes.MsgSubmitMisbehaviourResponse, error) + + // ConnectionOpenInit defines a rpc handler method for MsgConnectionOpenInit. + ConnectionOpenInit(goCtx context.Context, msg *connectiontypes.MsgConnectionOpenInit) (*connectiontypes.MsgConnectionOpenInitResponse, error) + + // ConnectionOpenTry defines a rpc handler method for MsgConnectionOpenTry. + ConnectionOpenTry(goCtx context.Context, msg *connectiontypes.MsgConnectionOpenTry) (*connectiontypes.MsgConnectionOpenTryResponse, error) + + // ConnectionOpenAck defines a rpc handler method for MsgConnectionOpenAck. + ConnectionOpenAck(goCtx context.Context, msg *connectiontypes.MsgConnectionOpenAck) (*connectiontypes.MsgConnectionOpenAckResponse, error) + + // ConnectionOpenConfirm defines a rpc handler method for MsgConnectionOpenConfirm. + ConnectionOpenConfirm(goCtx context.Context, msg *connectiontypes.MsgConnectionOpenConfirm) (*connectiontypes.MsgConnectionOpenConfirmResponse, error) + + // 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. + ChannelOpenInit(goCtx context.Context, msg *channeltypes.MsgChannelOpenInit) (*channeltypes.MsgChannelOpenInitResponse, error) + + // 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. + ChannelOpenTry(goCtx context.Context, msg *channeltypes.MsgChannelOpenTry) (*channeltypes.MsgChannelOpenTryResponse, error) + + // 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. + ChannelOpenAck(goCtx context.Context, msg *channeltypes.MsgChannelOpenAck) (*channeltypes.MsgChannelOpenAckResponse, error) + + // 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. + ChannelOpenConfirm(goCtx context.Context, msg *channeltypes.MsgChannelOpenConfirm) (*channeltypes.MsgChannelOpenConfirmResponse, error) + + // ChannelCloseInit defines a rpc handler method for MsgChannelCloseInit. + ChannelCloseInit(goCtx context.Context, msg *channeltypes.MsgChannelCloseInit) (*channeltypes.MsgChannelCloseInitResponse, error) + + // ChannelCloseConfirm defines a rpc handler method for MsgChannelCloseConfirm. + ChannelCloseConfirm(goCtx context.Context, msg *channeltypes.MsgChannelCloseConfirm) (*channeltypes.MsgChannelCloseConfirmResponse, error) + + // RecvPacket defines a rpc handler method for MsgRecvPacket. + RecvPacket(goCtx context.Context, msg *channeltypes.MsgRecvPacket) (*channeltypes.MsgRecvPacketResponse, error) + + // Timeout defines a rpc handler method for MsgTimeout. + Timeout(goCtx context.Context, msg *channeltypes.MsgTimeout) (*channeltypes.MsgTimeoutResponse, error) + + // TimeoutOnClose defines a rpc handler method for MsgTimeoutOnClose. + TimeoutOnClose(goCtx context.Context, msg *channeltypes.MsgTimeoutOnClose) (*channeltypes.MsgTimeoutOnCloseResponse, error) + + // Acknowledgement defines a rpc handler method for MsgAcknowledgement. + Acknowledgement(goCtx context.Context, msg *channeltypes.MsgAcknowledgement) (*channeltypes.MsgAcknowledgementResponse, error) +} diff --git a/modules/core/keeper/keeper.go b/modules/core/keeper/keeper.go index c4fe8959f67..8917dddeb2f 100644 --- a/modules/core/keeper/keeper.go +++ b/modules/core/keeper/keeper.go @@ -38,22 +38,22 @@ type Keeper struct { Router *porttypes.Router } -//IBCKeeper initialized in wasm code (in wasmd@v0.28.0/x/wasm/keeper/test_common.go:354) -//We need to maintain backward comptabile interface +// IBCKeeper initialized in wasm code (in wasmd@v0.28.0/x/wasm/keeper/test_common.go:354) +// We need to maintain backward comptabile interface func NewKeeper( cdc codec.BinaryCodec, key sdk.StoreKey, paramSpace paramtypes.Subspace, stakingKeeper clienttypes.StakingKeeper, upgradeKeeper clienttypes.UpgradeKeeper, scopedKeeper capabilitykeeper.ScopedKeeper, ) *Keeper { //Using dymint and nil as default. we can panic instead - return NewKeeperWithSelfClient(cdc, key, paramSpace, stakingKeeper, upgradeKeeper, scopedKeeper, ibcdmtypes.NewSelfClient(), nil) + return NewKeeperWithSelfClient(cdc, key, paramSpace, stakingKeeper, upgradeKeeper, scopedKeeper, ibcdmtypes.NewSelfClient()) } // NewKeeper creates a new ibc Keeper func NewKeeperWithSelfClient( cdc codec.BinaryCodec, key sdk.StoreKey, paramSpace paramtypes.Subspace, stakingKeeper clienttypes.StakingKeeper, upgradeKeeper clienttypes.UpgradeKeeper, - scopedKeeper capabilitykeeper.ScopedKeeper, selfClient exported.SelfClient, clientHooks exported.ClientHooks, + scopedKeeper capabilitykeeper.ScopedKeeper, selfClient exported.SelfClient, ) *Keeper { // register paramSpace at top level keeper // set KeyTable if it has not already been set @@ -76,7 +76,7 @@ func NewKeeperWithSelfClient( panic(fmt.Errorf("cannot initialize IBC keeper: empty scoped keeper")) } - clientKeeper := clientkeeper.NewKeeper(cdc, key, paramSpace, stakingKeeper, upgradeKeeper, selfClient, clientHooks) + clientKeeper := clientkeeper.NewKeeper(cdc, key, paramSpace, stakingKeeper, upgradeKeeper, selfClient) connectionKeeper := connectionkeeper.NewKeeper(cdc, key, paramSpace, clientKeeper) portKeeper := portkeeper.NewKeeper(scopedKeeper) channelKeeper := channelkeeper.NewKeeper(cdc, key, clientKeeper, connectionKeeper, portKeeper, scopedKeeper) diff --git a/modules/core/keeper/keeper_test.go b/modules/core/keeper/keeper_test.go index 4afa0dcad4b..c902ebc9065 100644 --- a/modules/core/keeper/keeper_test.go +++ b/modules/core/keeper/keeper_test.go @@ -72,7 +72,6 @@ func (suite *KeeperTestSuite) TestNewKeeper() { upgradeKeeper, scopedKeeper, ibctmtypes.NewSelfClient(), - nil, ) } ) diff --git a/modules/core/module.go b/modules/core/module.go index 0cca3e37f1e..645ad623a45 100644 --- a/modules/core/module.go +++ b/modules/core/module.go @@ -92,16 +92,27 @@ func (AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) // AppModule implements an application module for the ibc module. type AppModule struct { AppModuleBasic - keeper *keeper.Keeper + keeper *keeper.Keeper + msgSrvInterceptor IBCMsgI // create localhost by default createLocalhost bool } +// NewAppModuleWithMsgInterceptor creates a new AppModule object +// with customized message server for IBC messages +func NewAppModuleWithMsgInterceptor(k *keeper.Keeper, msgInterceptor IBCMsgI) AppModule { + return AppModule{ + keeper: k, + msgSrvInterceptor: msgInterceptor, + } +} + // NewAppModule creates a new AppModule object func NewAppModule(k *keeper.Keeper) AppModule { return AppModule{ - keeper: k, + keeper: k, + msgSrvInterceptor: k, } } @@ -132,9 +143,10 @@ func (am AppModule) LegacyQuerierHandler(legacyQuerierCdc *codec.LegacyAmino) sd // RegisterServices registers module services. func (am AppModule) RegisterServices(cfg module.Configurator) { - clienttypes.RegisterMsgServer(cfg.MsgServer(), am.keeper) - connectiontypes.RegisterMsgServer(cfg.MsgServer(), am.keeper) - channeltypes.RegisterMsgServer(cfg.MsgServer(), am.keeper) + clienttypes.RegisterMsgServer(cfg.MsgServer(), am.msgSrvInterceptor) + connectiontypes.RegisterMsgServer(cfg.MsgServer(), am.msgSrvInterceptor) + channeltypes.RegisterMsgServer(cfg.MsgServer(), am.msgSrvInterceptor) + types.RegisterQueryService(cfg.QueryServer(), am.keeper) m := clientkeeper.NewMigrator(am.keeper.ClientKeeper) diff --git a/testing/simapp/app.go b/testing/simapp/app.go index 748aa66280b..153e8876bc3 100644 --- a/testing/simapp/app.go +++ b/testing/simapp/app.go @@ -348,7 +348,7 @@ func NewSimAppWithConsensusType( } // Create IBC Keeper app.IBCKeeper = ibckeeper.NewKeeperWithSelfClient( - appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, selfClient, nil, + appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, selfClient, ) app.AuthzKeeper = authzkeeper.NewKeeper(keys[authzkeeper.StoreKey], appCodec, app.BaseApp.MsgServiceRouter()) From 408ecdc657df2e1ba6bcb74fa58d4ea094a8b80e Mon Sep 17 00:00:00 2001 From: Michael Tsitrin Date: Tue, 2 May 2023 23:27:05 +0300 Subject: [PATCH 131/140] compiles. removed in the meanwhile all related to self client. will be added back --- .../host/keeper/account.go | 23 ---------------- .../27-interchain-accounts/types/metadata.go | 8 ------ modules/apps/transfer/types/ack.go | 27 ------------------- modules/core/exported/client.go | 2 +- modules/core/ibc_msg_interceptor.go | 6 ++--- modules/light-clients/01-dymint/module.go | 2 +- .../01-dymint/types/client_state.go | 12 ++++----- .../01-dymint/types/client_state_test.go | 16 +++++------ .../light-clients/01-dymint/types/codec.go | 2 +- .../01-dymint/types/consensus_state.go | 6 ++--- .../01-dymint/types/consensus_state_test.go | 6 ++--- .../01-dymint/types/dymint.pb.go | 4 +-- .../01-dymint/types/dymint_test.go | 12 ++++----- .../light-clients/01-dymint/types/genesis.go | 4 +-- .../01-dymint/types/genesis_test.go | 6 ++--- .../light-clients/01-dymint/types/header.go | 6 ++--- .../01-dymint/types/header_test.go | 8 +++--- .../01-dymint/types/misbehaviour.go | 6 ++--- .../01-dymint/types/misbehaviour_handle.go | 4 +-- .../types/misbehaviour_handle_test.go | 12 ++++----- .../01-dymint/types/misbehaviour_test.go | 10 +++---- .../01-dymint/types/proposal_handle.go | 4 +-- .../01-dymint/types/proposal_handle_test.go | 10 +++---- .../01-dymint/types/self_client.go | 6 ++--- .../light-clients/01-dymint/types/store.go | 6 ++--- .../01-dymint/types/store_test.go | 14 +++++----- .../light-clients/01-dymint/types/update.go | 6 ++--- .../01-dymint/types/update_test.go | 12 ++++----- .../light-clients/01-dymint/types/upgrade.go | 6 ++--- .../01-dymint/types/upgrade_test.go | 10 +++---- .../07-tendermint/types/self_client.go | 6 ++--- testing/chain_dymint.go | 10 +++---- testing/chain_tendermint.go | 8 +++--- 33 files changed, 111 insertions(+), 169 deletions(-) delete mode 100644 modules/apps/transfer/types/ack.go diff --git a/modules/apps/27-interchain-accounts/host/keeper/account.go b/modules/apps/27-interchain-accounts/host/keeper/account.go index fb26868ba49..03e5c7ae0b8 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/account.go +++ b/modules/apps/27-interchain-accounts/host/keeper/account.go @@ -8,29 +8,6 @@ import ( icatypes "github.com/cosmos/ibc-go/v5/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, sdkerrors.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 -} - // 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. diff --git a/modules/apps/27-interchain-accounts/types/metadata.go b/modules/apps/27-interchain-accounts/types/metadata.go index a38e6cf0248..1cbd9dbe640 100644 --- a/modules/apps/27-interchain-accounts/types/metadata.go +++ b/modules/apps/27-interchain-accounts/types/metadata.go @@ -15,14 +15,6 @@ const ( TxTypeSDKMultiMsg = "sdk_multi_msg" ) -const ( - // EncodingProtobuf defines the protocol buffers proto3 encoding format - EncodingProtobuf = "proto3" - - // 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{ diff --git a/modules/apps/transfer/types/ack.go b/modules/apps/transfer/types/ack.go deleted file mode 100644 index 6512f2e8371..00000000000 --- a/modules/apps/transfer/types/ack.go +++ /dev/null @@ -1,27 +0,0 @@ -package types - -import ( - "fmt" - - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - - channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" -) - -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 on destination chain: see events for details" -) - -// NewErrorAcknowledgement returns a deterministic error string which may be used in -// the packet acknowledgement. -func NewErrorAcknowledgement(err error) channeltypes.Acknowledgement { - // the ABCI code is included in the abcitypes.ResponseDeliverTx hash - // constructed in Tendermint and is therefore deterministic - _, code, _ := sdkerrors.ABCIInfo(err, false) // discard non-determinstic codespace and log values - - errorString := fmt.Sprintf("ABCI code: %d: %s", code, ackErrorString) - - return channeltypes.NewErrorAcknowledgement(errorString) -} diff --git a/modules/core/exported/client.go b/modules/core/exported/client.go index 3d30391136f..f874aed85cd 100644 --- a/modules/core/exported/client.go +++ b/modules/core/exported/client.go @@ -46,7 +46,7 @@ type ClientState interface { proto.Message ClientType() string - GetChainID() string + // GetChainID() string GetLatestHeight() Height Validate() error diff --git a/modules/core/ibc_msg_interceptor.go b/modules/core/ibc_msg_interceptor.go index 779171b408e..c72812a0328 100644 --- a/modules/core/ibc_msg_interceptor.go +++ b/modules/core/ibc_msg_interceptor.go @@ -3,9 +3,9 @@ package ibc import ( "context" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - connectiontypes "github.com/cosmos/ibc-go/v3/modules/core/03-connection/types" - channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v5/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v5/modules/core/04-channel/types" ) type IBCMsgI interface { diff --git a/modules/light-clients/01-dymint/module.go b/modules/light-clients/01-dymint/module.go index 8f9e29b0e28..439d5dabd14 100644 --- a/modules/light-clients/01-dymint/module.go +++ b/modules/light-clients/01-dymint/module.go @@ -1,7 +1,7 @@ package dymint import ( - "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" + "github.com/cosmos/ibc-go/v5/modules/light-clients/01-dymint/types" ) // Name returns the IBC client name diff --git a/modules/light-clients/01-dymint/types/client_state.go b/modules/light-clients/01-dymint/types/client_state.go index 69aff098fd2..8d1bcff0c07 100644 --- a/modules/light-clients/01-dymint/types/client_state.go +++ b/modules/light-clients/01-dymint/types/client_state.go @@ -10,12 +10,12 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" tmtypes "github.com/tendermint/tendermint/types" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - connectiontypes "github.com/cosmos/ibc-go/v3/modules/core/03-connection/types" - channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" - commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" - host "github.com/cosmos/ibc-go/v3/modules/core/24-host" - "github.com/cosmos/ibc-go/v3/modules/core/exported" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v5/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v5/modules/core/04-channel/types" + commitmenttypes "github.com/cosmos/ibc-go/v5/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v5/modules/core/24-host" + "github.com/cosmos/ibc-go/v5/modules/core/exported" ) var _ exported.ClientState = (*ClientState)(nil) diff --git a/modules/light-clients/01-dymint/types/client_state_test.go b/modules/light-clients/01-dymint/types/client_state_test.go index 5c360f18bf4..46771571bff 100644 --- a/modules/light-clients/01-dymint/types/client_state_test.go +++ b/modules/light-clients/01-dymint/types/client_state_test.go @@ -5,14 +5,14 @@ import ( ics23 "github.com/confio/ics23/go" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" - commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" - host "github.com/cosmos/ibc-go/v3/modules/core/24-host" - "github.com/cosmos/ibc-go/v3/modules/core/exported" - "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" - ibctesting "github.com/cosmos/ibc-go/v3/testing" - ibcmock "github.com/cosmos/ibc-go/v3/testing/mock" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v5/modules/core/04-channel/types" + commitmenttypes "github.com/cosmos/ibc-go/v5/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v5/modules/core/24-host" + "github.com/cosmos/ibc-go/v5/modules/core/exported" + "github.com/cosmos/ibc-go/v5/modules/light-clients/01-dymint/types" + ibctesting "github.com/cosmos/ibc-go/v5/testing" + ibcmock "github.com/cosmos/ibc-go/v5/testing/mock" ) const ( diff --git a/modules/light-clients/01-dymint/types/codec.go b/modules/light-clients/01-dymint/types/codec.go index 20787ce0ed0..8156431f505 100644 --- a/modules/light-clients/01-dymint/types/codec.go +++ b/modules/light-clients/01-dymint/types/codec.go @@ -3,7 +3,7 @@ package types import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/ibc-go/v3/modules/core/exported" + "github.com/cosmos/ibc-go/v5/modules/core/exported" ) // RegisterInterfaces registers the dymint concrete client-related diff --git a/modules/light-clients/01-dymint/types/consensus_state.go b/modules/light-clients/01-dymint/types/consensus_state.go index f938e3cda1b..518c9c87675 100644 --- a/modules/light-clients/01-dymint/types/consensus_state.go +++ b/modules/light-clients/01-dymint/types/consensus_state.go @@ -6,9 +6,9 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" tmbytes "github.com/tendermint/tendermint/libs/bytes" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" - "github.com/cosmos/ibc-go/v3/modules/core/exported" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v5/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v5/modules/core/exported" ) // SentinelRoot is used as a stand-in root value for the consensus state set at the upgrade height diff --git a/modules/light-clients/01-dymint/types/consensus_state_test.go b/modules/light-clients/01-dymint/types/consensus_state_test.go index 9789f55f761..bb7483d54d6 100644 --- a/modules/light-clients/01-dymint/types/consensus_state_test.go +++ b/modules/light-clients/01-dymint/types/consensus_state_test.go @@ -3,9 +3,9 @@ package types_test import ( "time" - commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" - "github.com/cosmos/ibc-go/v3/modules/core/exported" - "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" + commitmenttypes "github.com/cosmos/ibc-go/v5/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v5/modules/core/exported" + "github.com/cosmos/ibc-go/v5/modules/light-clients/01-dymint/types" ) func (suite *DymintTestSuite) TestConsensusStateValidateBasic() { diff --git a/modules/light-clients/01-dymint/types/dymint.pb.go b/modules/light-clients/01-dymint/types/dymint.pb.go index 31f0b4ab57b..dc81b06dce1 100644 --- a/modules/light-clients/01-dymint/types/dymint.pb.go +++ b/modules/light-clients/01-dymint/types/dymint.pb.go @@ -6,8 +6,8 @@ package types import ( fmt "fmt" _go "github.com/confio/ics23/go" - types "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - types1 "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" + types "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + types1 "github.com/cosmos/ibc-go/v5/modules/core/23-commitment/types" _ "github.com/gogo/protobuf/gogoproto" proto "github.com/gogo/protobuf/proto" github_com_gogo_protobuf_types "github.com/gogo/protobuf/types" diff --git a/modules/light-clients/01-dymint/types/dymint_test.go b/modules/light-clients/01-dymint/types/dymint_test.go index b0a01b21181..fcac1dd6ce8 100644 --- a/modules/light-clients/01-dymint/types/dymint_test.go +++ b/modules/light-clients/01-dymint/types/dymint_test.go @@ -11,12 +11,12 @@ import ( tmproto "github.com/tendermint/tendermint/proto/tendermint/types" tmtypes "github.com/tendermint/tendermint/types" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - "github.com/cosmos/ibc-go/v3/modules/core/exported" - ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" - ibctesting "github.com/cosmos/ibc-go/v3/testing" - ibctestingmock "github.com/cosmos/ibc-go/v3/testing/mock" - "github.com/cosmos/ibc-go/v3/testing/simapp" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v5/modules/core/exported" + ibctmtypes "github.com/cosmos/ibc-go/v5/modules/light-clients/01-dymint/types" + ibctesting "github.com/cosmos/ibc-go/v5/testing" + ibctestingmock "github.com/cosmos/ibc-go/v5/testing/mock" + "github.com/cosmos/ibc-go/v5/testing/simapp" ) const ( diff --git a/modules/light-clients/01-dymint/types/genesis.go b/modules/light-clients/01-dymint/types/genesis.go index 82a996d3696..8f9a8731a80 100644 --- a/modules/light-clients/01-dymint/types/genesis.go +++ b/modules/light-clients/01-dymint/types/genesis.go @@ -3,8 +3,8 @@ package types import ( sdk "github.com/cosmos/cosmos-sdk/types" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - "github.com/cosmos/ibc-go/v3/modules/core/exported" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v5/modules/core/exported" ) // ExportMetadata exports all the consensus metadata in the client store so they can be included in clients genesis diff --git a/modules/light-clients/01-dymint/types/genesis_test.go b/modules/light-clients/01-dymint/types/genesis_test.go index e1b71713c98..1466ab34b4b 100644 --- a/modules/light-clients/01-dymint/types/genesis_test.go +++ b/modules/light-clients/01-dymint/types/genesis_test.go @@ -3,9 +3,9 @@ package types_test import ( sdk "github.com/cosmos/cosmos-sdk/types" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" - ibctesting "github.com/cosmos/ibc-go/v3/testing" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v5/modules/light-clients/01-dymint/types" + ibctesting "github.com/cosmos/ibc-go/v5/testing" ) // expected export ordering: diff --git a/modules/light-clients/01-dymint/types/header.go b/modules/light-clients/01-dymint/types/header.go index 40a57dc668c..aedf855365d 100644 --- a/modules/light-clients/01-dymint/types/header.go +++ b/modules/light-clients/01-dymint/types/header.go @@ -8,9 +8,9 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" tmtypes "github.com/tendermint/tendermint/types" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" - "github.com/cosmos/ibc-go/v3/modules/core/exported" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v5/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v5/modules/core/exported" ) var _ exported.Header = &Header{} diff --git a/modules/light-clients/01-dymint/types/header_test.go b/modules/light-clients/01-dymint/types/header_test.go index b0f55e1853e..fbf98f34493 100644 --- a/modules/light-clients/01-dymint/types/header_test.go +++ b/modules/light-clients/01-dymint/types/header_test.go @@ -6,10 +6,10 @@ import ( tmprotocrypto "github.com/tendermint/tendermint/proto/tendermint/crypto" tmprototypes "github.com/tendermint/tendermint/proto/tendermint/types" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - "github.com/cosmos/ibc-go/v3/modules/core/exported" - "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" - ibctesting "github.com/cosmos/ibc-go/v3/testing" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v5/modules/core/exported" + "github.com/cosmos/ibc-go/v5/modules/light-clients/01-dymint/types" + ibctesting "github.com/cosmos/ibc-go/v5/testing" ) func (suite *DymintTestSuite) TestGetHeight() { diff --git a/modules/light-clients/01-dymint/types/misbehaviour.go b/modules/light-clients/01-dymint/types/misbehaviour.go index 78d4961bcd8..1132f07a51d 100644 --- a/modules/light-clients/01-dymint/types/misbehaviour.go +++ b/modules/light-clients/01-dymint/types/misbehaviour.go @@ -5,9 +5,9 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - host "github.com/cosmos/ibc-go/v3/modules/core/24-host" - "github.com/cosmos/ibc-go/v3/modules/core/exported" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v5/modules/core/24-host" + "github.com/cosmos/ibc-go/v5/modules/core/exported" ) var _ exported.Misbehaviour = &Misbehaviour{} diff --git a/modules/light-clients/01-dymint/types/misbehaviour_handle.go b/modules/light-clients/01-dymint/types/misbehaviour_handle.go index 95393762271..c102612305f 100644 --- a/modules/light-clients/01-dymint/types/misbehaviour_handle.go +++ b/modules/light-clients/01-dymint/types/misbehaviour_handle.go @@ -10,8 +10,8 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" tmtypes "github.com/tendermint/tendermint/types" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - "github.com/cosmos/ibc-go/v3/modules/core/exported" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v5/modules/core/exported" ) // CheckMisbehaviourAndUpdateState determines whether or not two conflicting diff --git a/modules/light-clients/01-dymint/types/misbehaviour_handle_test.go b/modules/light-clients/01-dymint/types/misbehaviour_handle_test.go index 50be1356980..0318aab82d4 100644 --- a/modules/light-clients/01-dymint/types/misbehaviour_handle_test.go +++ b/modules/light-clients/01-dymint/types/misbehaviour_handle_test.go @@ -7,12 +7,12 @@ import ( "github.com/tendermint/tendermint/crypto/tmhash" tmtypes "github.com/tendermint/tendermint/types" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" - "github.com/cosmos/ibc-go/v3/modules/core/exported" - "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" - ibctesting "github.com/cosmos/ibc-go/v3/testing" - ibctestingmock "github.com/cosmos/ibc-go/v3/testing/mock" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v5/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v5/modules/core/exported" + "github.com/cosmos/ibc-go/v5/modules/light-clients/01-dymint/types" + ibctesting "github.com/cosmos/ibc-go/v5/testing" + ibctestingmock "github.com/cosmos/ibc-go/v5/testing/mock" ) func (suite *DymintTestSuite) TestCheckMisbehaviourAndUpdateState() { diff --git a/modules/light-clients/01-dymint/types/misbehaviour_test.go b/modules/light-clients/01-dymint/types/misbehaviour_test.go index d5b4c3f584b..63f4f605347 100644 --- a/modules/light-clients/01-dymint/types/misbehaviour_test.go +++ b/modules/light-clients/01-dymint/types/misbehaviour_test.go @@ -7,11 +7,11 @@ import ( tmproto "github.com/tendermint/tendermint/proto/tendermint/types" tmtypes "github.com/tendermint/tendermint/types" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - "github.com/cosmos/ibc-go/v3/modules/core/exported" - "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" - ibctesting "github.com/cosmos/ibc-go/v3/testing" - ibctestingmock "github.com/cosmos/ibc-go/v3/testing/mock" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v5/modules/core/exported" + "github.com/cosmos/ibc-go/v5/modules/light-clients/01-dymint/types" + ibctesting "github.com/cosmos/ibc-go/v5/testing" + ibctestingmock "github.com/cosmos/ibc-go/v5/testing/mock" ) func (suite *DymintTestSuite) TestMisbehaviour() { diff --git a/modules/light-clients/01-dymint/types/proposal_handle.go b/modules/light-clients/01-dymint/types/proposal_handle.go index a45053ed34c..8c7908f577d 100644 --- a/modules/light-clients/01-dymint/types/proposal_handle.go +++ b/modules/light-clients/01-dymint/types/proposal_handle.go @@ -8,8 +8,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - "github.com/cosmos/ibc-go/v3/modules/core/exported" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v5/modules/core/exported" ) // CheckSubstituteAndUpdateState will try to update the client with the state of the diff --git a/modules/light-clients/01-dymint/types/proposal_handle_test.go b/modules/light-clients/01-dymint/types/proposal_handle_test.go index 1e8a521ff81..22e89d4b597 100644 --- a/modules/light-clients/01-dymint/types/proposal_handle_test.go +++ b/modules/light-clients/01-dymint/types/proposal_handle_test.go @@ -4,11 +4,11 @@ import ( fmt "fmt" "time" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - "github.com/cosmos/ibc-go/v3/modules/core/exported" - "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" - tmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" - ibctesting "github.com/cosmos/ibc-go/v3/testing" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v5/modules/core/exported" + "github.com/cosmos/ibc-go/v5/modules/light-clients/01-dymint/types" + tmtypes "github.com/cosmos/ibc-go/v5/modules/light-clients/07-tendermint/types" + ibctesting "github.com/cosmos/ibc-go/v5/testing" ) var frozenHeight = clienttypes.NewHeight(0, 1) diff --git a/modules/light-clients/01-dymint/types/self_client.go b/modules/light-clients/01-dymint/types/self_client.go index e5b7db85075..c883a8d248c 100644 --- a/modules/light-clients/01-dymint/types/self_client.go +++ b/modules/light-clients/01-dymint/types/self_client.go @@ -10,9 +10,9 @@ import ( tmproto "github.com/tendermint/tendermint/proto/tendermint/types" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" - "github.com/cosmos/ibc-go/v3/modules/core/exported" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v5/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v5/modules/core/exported" ) var _ exported.SelfClient = (*SelfClient)(nil) diff --git a/modules/light-clients/01-dymint/types/store.go b/modules/light-clients/01-dymint/types/store.go index ca701b1eb85..0142acd47d8 100644 --- a/modules/light-clients/01-dymint/types/store.go +++ b/modules/light-clients/01-dymint/types/store.go @@ -10,9 +10,9 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - host "github.com/cosmos/ibc-go/v3/modules/core/24-host" - "github.com/cosmos/ibc-go/v3/modules/core/exported" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v5/modules/core/24-host" + "github.com/cosmos/ibc-go/v5/modules/core/exported" ) /* diff --git a/modules/light-clients/01-dymint/types/store_test.go b/modules/light-clients/01-dymint/types/store_test.go index 86500130482..b00e485470e 100644 --- a/modules/light-clients/01-dymint/types/store_test.go +++ b/modules/light-clients/01-dymint/types/store_test.go @@ -4,13 +4,13 @@ import ( "math" "time" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" - host "github.com/cosmos/ibc-go/v3/modules/core/24-host" - "github.com/cosmos/ibc-go/v3/modules/core/exported" - "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" - solomachinetypes "github.com/cosmos/ibc-go/v3/modules/light-clients/06-solomachine/types" - ibctesting "github.com/cosmos/ibc-go/v3/testing" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v5/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v5/modules/core/24-host" + "github.com/cosmos/ibc-go/v5/modules/core/exported" + "github.com/cosmos/ibc-go/v5/modules/light-clients/01-dymint/types" + solomachinetypes "github.com/cosmos/ibc-go/v5/modules/light-clients/06-solomachine/types" + ibctesting "github.com/cosmos/ibc-go/v5/testing" ) func (suite *DymintTestSuite) TestGetConsensusState() { diff --git a/modules/light-clients/01-dymint/types/update.go b/modules/light-clients/01-dymint/types/update.go index 8c9162b85fc..6c675337379 100644 --- a/modules/light-clients/01-dymint/types/update.go +++ b/modules/light-clients/01-dymint/types/update.go @@ -11,9 +11,9 @@ import ( "github.com/tendermint/tendermint/light" tmtypes "github.com/tendermint/tendermint/types" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" - "github.com/cosmos/ibc-go/v3/modules/core/exported" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v5/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v5/modules/core/exported" ) // CheckHeaderAndUpdateState checks if the provided header is valid, and if valid it will: diff --git a/modules/light-clients/01-dymint/types/update_test.go b/modules/light-clients/01-dymint/types/update_test.go index 6c23b8eadd9..4c150883859 100644 --- a/modules/light-clients/01-dymint/types/update_test.go +++ b/modules/light-clients/01-dymint/types/update_test.go @@ -6,12 +6,12 @@ import ( tmtypes "github.com/tendermint/tendermint/types" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" - "github.com/cosmos/ibc-go/v3/modules/core/exported" - types "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" - ibctesting "github.com/cosmos/ibc-go/v3/testing" - ibctestingmock "github.com/cosmos/ibc-go/v3/testing/mock" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v5/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v5/modules/core/exported" + types "github.com/cosmos/ibc-go/v5/modules/light-clients/01-dymint/types" + ibctesting "github.com/cosmos/ibc-go/v5/testing" + ibctestingmock "github.com/cosmos/ibc-go/v5/testing/mock" ) func (suite *DymintTestSuite) TestCheckHeaderAndUpdateState() { diff --git a/modules/light-clients/01-dymint/types/upgrade.go b/modules/light-clients/01-dymint/types/upgrade.go index a1fd328bd66..d5b0761b4f7 100644 --- a/modules/light-clients/01-dymint/types/upgrade.go +++ b/modules/light-clients/01-dymint/types/upgrade.go @@ -8,9 +8,9 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" - "github.com/cosmos/ibc-go/v3/modules/core/exported" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v5/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v5/modules/core/exported" ) // VerifyUpgradeAndUpdateState checks if the upgraded client has been committed by the current client diff --git a/modules/light-clients/01-dymint/types/upgrade_test.go b/modules/light-clients/01-dymint/types/upgrade_test.go index f8e98920956..d91c5c3c73a 100644 --- a/modules/light-clients/01-dymint/types/upgrade_test.go +++ b/modules/light-clients/01-dymint/types/upgrade_test.go @@ -3,11 +3,11 @@ package types_test import ( upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" - "github.com/cosmos/ibc-go/v3/modules/core/exported" - "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" - ibctesting "github.com/cosmos/ibc-go/v3/testing" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v5/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v5/modules/core/exported" + "github.com/cosmos/ibc-go/v5/modules/light-clients/01-dymint/types" + ibctesting "github.com/cosmos/ibc-go/v5/testing" ) var ( diff --git a/modules/light-clients/07-tendermint/types/self_client.go b/modules/light-clients/07-tendermint/types/self_client.go index 320eb1d84c7..e7f8bc058e3 100644 --- a/modules/light-clients/07-tendermint/types/self_client.go +++ b/modules/light-clients/07-tendermint/types/self_client.go @@ -11,9 +11,9 @@ import ( tmproto "github.com/tendermint/tendermint/proto/tendermint/types" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" - "github.com/cosmos/ibc-go/v3/modules/core/exported" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v5/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v5/modules/core/exported" ) var _ exported.SelfClient = (*SelfClient)(nil) diff --git a/testing/chain_dymint.go b/testing/chain_dymint.go index 4925330b831..c27fbe27ee1 100644 --- a/testing/chain_dymint.go +++ b/testing/chain_dymint.go @@ -13,11 +13,11 @@ import ( tmtypes "github.com/tendermint/tendermint/types" tmversion "github.com/tendermint/tendermint/version" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" - "github.com/cosmos/ibc-go/v3/modules/core/exported" - ibcdmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/01-dymint/types" - "github.com/cosmos/ibc-go/v3/testing/mock" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v5/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v5/modules/core/exported" + ibcdmtypes "github.com/cosmos/ibc-go/v5/modules/light-clients/01-dymint/types" + "github.com/cosmos/ibc-go/v5/testing/mock" ) type DymintConfig struct { diff --git a/testing/chain_tendermint.go b/testing/chain_tendermint.go index a5147a9af9a..2bbfcc12bad 100644 --- a/testing/chain_tendermint.go +++ b/testing/chain_tendermint.go @@ -14,10 +14,10 @@ import ( tmtypes "github.com/tendermint/tendermint/types" tmversion "github.com/tendermint/tendermint/version" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" - "github.com/cosmos/ibc-go/v3/modules/core/exported" - ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v5/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v5/modules/core/exported" + ibctmtypes "github.com/cosmos/ibc-go/v5/modules/light-clients/07-tendermint/types" ) type TendermintConfig struct { From e157265eced7437c60edaa9061d03291a931315f Mon Sep 17 00:00:00 2001 From: Michael Tsitrin Date: Wed, 3 May 2023 09:31:32 +0300 Subject: [PATCH 132/140] running keeper with Dymint light client --- go.mod | 7 --- go.sum | 21 ++++++- modules/core/02-client/keeper/keeper.go | 79 ++++--------------------- modules/core/types/codec.go | 2 + 4 files changed, 33 insertions(+), 76 deletions(-) diff --git a/go.mod b/go.mod index 9e82a7e856f..c947e86ddc0 100644 --- a/go.mod +++ b/go.mod @@ -154,13 +154,6 @@ require ( sigs.k8s.io/yaml v1.3.0 // indirect ) -replace ( - // dragonberry replace for ics23 - github.com/confio/ics23/go => github.com/cosmos/cosmos-sdk/ics23/go v0.8.0 - - github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 -) - replace ( // dragonberry replace for ics23 github.com/confio/ics23/go => github.com/cosmos/cosmos-sdk/ics23/go v0.8.0 diff --git a/go.sum b/go.sum index 21f33153968..f373a746dde 100644 --- a/go.sum +++ b/go.sum @@ -167,6 +167,7 @@ github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+q github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= 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/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= @@ -335,7 +336,6 @@ github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/gin-gonic/gin v1.7.0 h1:jGB9xAJQ12AIGNB4HguylppmDK1Am9ppF7XnGXXJuoU= github.com/gin-gonic/gin v1.7.0/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= @@ -406,7 +406,9 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt 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= @@ -423,6 +425,7 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD 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 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -443,6 +446,7 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ 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= @@ -649,6 +653,7 @@ github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 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/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= @@ -676,6 +681,7 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +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 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -1062,6 +1068,7 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu 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= @@ -1114,11 +1121,13 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 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-20210220033124-5f55cee0dc0d/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-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -1180,6 +1189,7 @@ golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190130150945-aca44879d564/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-20190222072716-a9d3bda3a223/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= @@ -1241,8 +1251,11 @@ golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7w 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= @@ -1349,6 +1362,11 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f 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/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= @@ -1565,7 +1583,6 @@ google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw 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 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 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= diff --git a/modules/core/02-client/keeper/keeper.go b/modules/core/02-client/keeper/keeper.go index 81ca1488ba7..9adf2bcc849 100644 --- a/modules/core/02-client/keeper/keeper.go +++ b/modules/core/02-client/keeper/keeper.go @@ -2,7 +2,6 @@ package keeper import ( "fmt" - "reflect" "strings" "github.com/cosmos/cosmos-sdk/codec" @@ -13,13 +12,11 @@ import ( paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" "github.com/tendermint/tendermint/libs/log" - "github.com/tendermint/tendermint/light" "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" - commitmenttypes "github.com/cosmos/ibc-go/v5/modules/core/23-commitment/types" host "github.com/cosmos/ibc-go/v5/modules/core/24-host" "github.com/cosmos/ibc-go/v5/modules/core/exported" - ibctmtypes "github.com/cosmos/ibc-go/v5/modules/light-clients/07-tendermint/types" + ibcdtypes "github.com/cosmos/ibc-go/v5/modules/light-clients/01-dymint/types" ) // Keeper represents a type that grants read and write permissions to any client @@ -257,77 +254,25 @@ func (k Keeper) GetSelfConsensusState(ctx sdk.Context, height exported.Height) ( return nil, sdkerrors.Wrapf(sdkerrors.ErrNotFound, "no historical info found at height %d", selfHeight.RevisionHeight) } - consensusState := &ibctmtypes.ConsensusState{ - Timestamp: histInfo.Header.Time, - Root: commitmenttypes.NewMerkleRoot(histInfo.Header.GetAppHash()), - NextValidatorsHash: histInfo.Header.NextValidatorsHash, + blockHeader, err := histInfo.Header.Marshal() + if err != nil { + return nil, err } - return consensusState, nil + + sc := ibcdtypes.NewSelfClient() + return sc.GetSelfConsensusStateFromBlocHeader(k.cdc, blockHeader) } // ValidateSelfClient validates the client parameters for a client of the running chain // This function is only used to validate the client state the counterparty stores for this chain // Client must be in same revision as the executing chain func (k Keeper) ValidateSelfClient(ctx sdk.Context, clientState exported.ClientState) error { - tmClient, ok := clientState.(*ibctmtypes.ClientState) - if !ok { - return sdkerrors.Wrapf(types.ErrInvalidClient, "client must be a Tendermint client, expected: %T, got: %T", - &ibctmtypes.ClientState{}, tmClient) - } - - if !tmClient.FrozenHeight.IsZero() { - return types.ErrClientFrozen - } - - if ctx.ChainID() != tmClient.ChainId { - return sdkerrors.Wrapf(types.ErrInvalidClient, "invalid chain-id. expected: %s, got: %s", - ctx.ChainID(), tmClient.ChainId) - } - - revision := types.ParseChainID(ctx.ChainID()) - - // client must be in the same revision as executing chain - if tmClient.LatestHeight.RevisionNumber != revision { - return sdkerrors.Wrapf(types.ErrInvalidClient, "client is not in the same revision as the chain. expected revision: %d, got: %d", - tmClient.LatestHeight.RevisionNumber, revision) - } - - selfHeight := types.NewHeight(revision, uint64(ctx.BlockHeight())) - if tmClient.LatestHeight.GTE(selfHeight) { - return sdkerrors.Wrapf(types.ErrInvalidClient, "client has LatestHeight %d greater than or equal to chain height %d", - tmClient.LatestHeight, selfHeight) - } - - expectedProofSpecs := commitmenttypes.GetSDKSpecs() - if !reflect.DeepEqual(expectedProofSpecs, tmClient.ProofSpecs) { - return sdkerrors.Wrapf(types.ErrInvalidClient, "client has invalid proof specs. expected: %v got: %v", - expectedProofSpecs, tmClient.ProofSpecs) - } - - if err := light.ValidateTrustLevel(tmClient.TrustLevel.ToTendermint()); err != nil { - return sdkerrors.Wrapf(types.ErrInvalidClient, "trust-level invalid: %v", err) - } - - expectedUbdPeriod := k.stakingKeeper.UnbondingTime(ctx) - if expectedUbdPeriod != tmClient.UnbondingPeriod { - return sdkerrors.Wrapf(types.ErrInvalidClient, "invalid unbonding period. expected: %s, got: %s", - expectedUbdPeriod, tmClient.UnbondingPeriod) - } - - if tmClient.UnbondingPeriod < tmClient.TrustingPeriod { - return sdkerrors.Wrapf(types.ErrInvalidClient, "unbonding period must be greater than trusting period. unbonding period (%d) < trusting period (%d)", - tmClient.UnbondingPeriod, tmClient.TrustingPeriod) - } - - if len(tmClient.UpgradePath) != 0 { - // For now, SDK IBC implementation assumes that upgrade path (if defined) is defined by SDK upgrade module - expectedUpgradePath := []string{upgradetypes.StoreKey, upgradetypes.KeyUpgradedIBCState} - if !reflect.DeepEqual(expectedUpgradePath, tmClient.UpgradePath) { - return sdkerrors.Wrapf(types.ErrInvalidClient, "upgrade path must be the upgrade path defined by upgrade module. expected %v, got %v", - expectedUpgradePath, tmClient.UpgradePath) - } + if exported.Dymint != clientState.ClientType() { + return sdkerrors.Wrapf(types.ErrInvalidClient, "invalid client type. expected: %s, got: %s", + exported.Dymint, clientState.ClientType()) } - return nil + sc := ibcdtypes.NewSelfClient() + return sc.ValidateSelfClientState(ctx, k.stakingKeeper.UnbondingTime(ctx), clientState) } // GetUpgradePlan executes the upgrade keeper GetUpgradePlan function. diff --git a/modules/core/types/codec.go b/modules/core/types/codec.go index d597141005f..83abfb549a3 100644 --- a/modules/core/types/codec.go +++ b/modules/core/types/codec.go @@ -7,6 +7,7 @@ import ( connectiontypes "github.com/cosmos/ibc-go/v5/modules/core/03-connection/types" channeltypes "github.com/cosmos/ibc-go/v5/modules/core/04-channel/types" commitmenttypes "github.com/cosmos/ibc-go/v5/modules/core/23-commitment/types" + ibcdmtypes "github.com/cosmos/ibc-go/v5/modules/light-clients/01-dymint/types" solomachinetypes "github.com/cosmos/ibc-go/v5/modules/light-clients/06-solomachine/types" ibctmtypes "github.com/cosmos/ibc-go/v5/modules/light-clients/07-tendermint/types" localhosttypes "github.com/cosmos/ibc-go/v5/modules/light-clients/09-localhost/types" @@ -17,6 +18,7 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) { clienttypes.RegisterInterfaces(registry) connectiontypes.RegisterInterfaces(registry) channeltypes.RegisterInterfaces(registry) + ibcdmtypes.RegisterInterfaces(registry) solomachinetypes.RegisterInterfaces(registry) ibctmtypes.RegisterInterfaces(registry) localhosttypes.RegisterInterfaces(registry) From 7eecb1058a96c3bfe55edbbc65f3e783e8554cea Mon Sep 17 00:00:00 2001 From: Michael Tsitrin Date: Wed, 3 May 2023 13:12:26 +0300 Subject: [PATCH 133/140] cleanup after comparing with origin/v5.1.0 --- CHANGELOG.md | 129 ------------------ docs/apps/interchain-accounts/integration.md | 4 +- docs/ibc/integration.md | 4 +- docs/migrations/v2-to-v3.md | 6 - .../host/keeper/account.go | 2 +- .../27-interchain-accounts/types/account.go | 13 -- .../types/account_test.go | 2 +- modules/core/02-client/keeper/keeper.go | 1 + .../core/02-client/legacy/v100/solomachine.go | 5 - modules/core/02-client/types/codec_test.go | 6 +- .../core/03-connection/keeper/keeper_test.go | 4 +- modules/core/exported/client.go | 13 +- .../06-solomachine/types/client_state.go | 5 - .../06-solomachine/types/header.go | 5 - .../06-solomachine/types/misbehaviour.go | 5 - .../07-tendermint/types/header.go | 5 - .../07-tendermint/types/misbehaviour.go | 6 - .../07-tendermint/types/store_test.go | 4 +- testing/simapp/test_helpers.go | 2 +- 19 files changed, 20 insertions(+), 201 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac338b1b8e3..2e58850e5b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -139,135 +139,6 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (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 - -### API Breaking - -### State Machine Breaking - -### Improvements - -### Features - -### Bug Fixes - -## [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) [\#2580](https://github.com/cosmos/ibc-go/issues/2580) 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.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 endpont 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.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.0](https://github.com/cosmos/ibc-go/releases/tag/v3.1.0) - 2022-04-16 - -### 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-04-16 - -### 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 - -* (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. -* (modules/core/04-channel)[\#1919](https://github.com/cosmos/ibc-go/pull/1919) Fixed formatting of sequence for packet "acknowledgement written" logs. - - ## [v3.0.0](https://github.com/cosmos/ibc-go/releases/tag/v3.0.0) - 2022-03-15 ### Dependencies diff --git a/docs/apps/interchain-accounts/integration.md b/docs/apps/interchain-accounts/integration.md index 90f3c968cce..3010ba11984 100644 --- a/docs/apps/interchain-accounts/integration.md +++ b/docs/apps/interchain-accounts/integration.md @@ -117,8 +117,8 @@ app.moduleManager = module.NewManager( ... -// Add fee middleware to begin blocker logic -app.moduleManager.SetOrderBeginBlockers( +// Add Interchain Accounts module InitGenesis logic +app.mm.SetOrderInitGenesis( ... icatypes.ModuleName, ... diff --git a/docs/ibc/integration.md b/docs/ibc/integration.md index 9536feacba1..fba5798eadd 100644 --- a/docs/ibc/integration.md +++ b/docs/ibc/integration.md @@ -177,8 +177,8 @@ connection handhake. 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 +well. This is optional as it is only required if your application uses the [localhost +client](https://github.com/cosmos/ibc/blob/master/spec/client/ics-009-loopback-client) to connect two different modules from the same chain. ::: tip diff --git a/docs/migrations/v2-to-v3.md b/docs/migrations/v2-to-v3.md index 16ad9b8cfd6..d12d3708df3 100644 --- a/docs/migrations/v2-to-v3.md +++ b/docs/migrations/v2-to-v3.md @@ -18,12 +18,6 @@ No genesis or in-place migrations are required when upgrading from v1 or v2 of i ## Chains -### IS04 - 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`. - - ### ICS20 The `transferkeeper.NewKeeper(...)` now takes in an ICS4Wrapper. diff --git a/modules/apps/27-interchain-accounts/host/keeper/account.go b/modules/apps/27-interchain-accounts/host/keeper/account.go index 03e5c7ae0b8..db14f1d1cff 100644 --- a/modules/apps/27-interchain-accounts/host/keeper/account.go +++ b/modules/apps/27-interchain-accounts/host/keeper/account.go @@ -12,7 +12,7 @@ import ( // 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.GenerateUniqueAddress(ctx, connectionID, controllerPortID) + accAddress := icatypes.GenerateAddress(ctx, connectionID, controllerPortID) if acc := k.accountKeeper.GetAccount(ctx, accAddress); acc != nil { return nil, sdkerrors.Wrapf(icatypes.ErrAccountAlreadyExist, "existing account for newly generated interchain account address %s", accAddress) diff --git a/modules/apps/27-interchain-accounts/types/account.go b/modules/apps/27-interchain-accounts/types/account.go index 3247b17f82f..8c44b277a9f 100644 --- a/modules/apps/27-interchain-accounts/types/account.go +++ b/modules/apps/27-interchain-accounts/types/account.go @@ -52,19 +52,6 @@ func GenerateAddress(ctx sdk.Context, connectionID, portID string) sdk.AccAddres return sdkaddress.Derive(hostModuleAcc, buf) } -// GenerateUniqueAddress 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 GenerateUniqueAddress(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...) - buf = append(buf, header.DataHash...) - - 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 { diff --git a/modules/apps/27-interchain-accounts/types/account_test.go b/modules/apps/27-interchain-accounts/types/account_test.go index 7aa3e1997f7..36d75f5d1fb 100644 --- a/modules/apps/27-interchain-accounts/types/account_test.go +++ b/modules/apps/27-interchain-accounts/types/account_test.go @@ -46,7 +46,7 @@ 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, "TestGenerateUniqueAddress failed") + suite.Require().NoError(err, "TestGenerateAddress failed") suite.Require().NotEmpty(accAddr) } diff --git a/modules/core/02-client/keeper/keeper.go b/modules/core/02-client/keeper/keeper.go index 9adf2bcc849..97aab2de87b 100644 --- a/modules/core/02-client/keeper/keeper.go +++ b/modules/core/02-client/keeper/keeper.go @@ -259,6 +259,7 @@ func (k Keeper) GetSelfConsensusState(ctx sdk.Context, height exported.Height) ( return nil, err } + //FIXME: refactor this. should be methods of the keeper sc := ibcdtypes.NewSelfClient() return sc.GetSelfConsensusStateFromBlocHeader(k.cdc, blockHeader) } diff --git a/modules/core/02-client/legacy/v100/solomachine.go b/modules/core/02-client/legacy/v100/solomachine.go index 2b849c8fe13..e9121760e5b 100644 --- a/modules/core/02-client/legacy/v100/solomachine.go +++ b/modules/core/02-client/legacy/v100/solomachine.go @@ -53,11 +53,6 @@ func (cs ClientState) ClientType() string { panic("legacy solo machine is deprecated!") } -// GetChainID panics! -func (cs ClientState) GetChainID() string { - panic("legacy solo machine is deprecated!") -} - // GetLatestHeight panics! func (cs ClientState) GetLatestHeight() exported.Height { panic("legacy solo machine is deprecated!") diff --git a/modules/core/02-client/types/codec_test.go b/modules/core/02-client/types/codec_test.go index 1700b3fcb5f..e8bfb0b1beb 100644 --- a/modules/core/02-client/types/codec_test.go +++ b/modules/core/02-client/types/codec_test.go @@ -82,7 +82,7 @@ func (suite *TypesTestSuite) TestPackConsensusState() { }, { "tendermint consensus", - suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint).LastHeader.ConsensusState(), + suite.chainA.LastHeader.ConsensusState(), true, }, { @@ -128,7 +128,7 @@ func (suite *TypesTestSuite) TestPackHeader() { }, { "tendermint header", - suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint).LastHeader, + suite.chainA.LastHeader, true, }, { @@ -175,7 +175,7 @@ func (suite *TypesTestSuite) TestPackMisbehaviour() { }, { "tendermint misbehaviour", - ibctmtypes.NewMisbehaviour("tendermint", suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint).LastHeader, suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint).LastHeader), + ibctmtypes.NewMisbehaviour("tendermint", suite.chainA.LastHeader, suite.chainA.LastHeader), true, }, { diff --git a/modules/core/03-connection/keeper/keeper_test.go b/modules/core/03-connection/keeper/keeper_test.go index d3bfceac861..df1a6131d3e 100644 --- a/modules/core/03-connection/keeper/keeper_test.go +++ b/modules/core/03-connection/keeper/keeper_test.go @@ -136,12 +136,12 @@ func (suite *KeeperTestSuite) TestGetTimestampAtHeight() { tc.malleate() actualTimestamp, err := suite.chainA.App.GetIBCKeeper().ConnectionKeeper.GetTimestampAtHeight( - suite.chainA.GetContext(), connection, suite.chainB.TestChainClient.(*ibctesting.TestChainTendermint).LastHeader.GetHeight(), + suite.chainA.GetContext(), connection, suite.chainB.LastHeader.GetHeight(), ) if tc.expPass { suite.Require().NoError(err) - suite.Require().EqualValues(uint64(suite.chainB.TestChainClient.(*ibctesting.TestChainTendermint).LastHeader.GetTime().UnixNano()), actualTimestamp) + suite.Require().EqualValues(uint64(suite.chainB.LastHeader.GetTime().UnixNano()), actualTimestamp) } else { suite.Require().Error(err) } diff --git a/modules/core/exported/client.go b/modules/core/exported/client.go index f874aed85cd..cbe7e104bf0 100644 --- a/modules/core/exported/client.go +++ b/modules/core/exported/client.go @@ -46,7 +46,6 @@ type ClientState interface { proto.Message ClientType() string - // GetChainID() string GetLatestHeight() Height Validate() error @@ -205,7 +204,6 @@ type Misbehaviour interface { proto.Message ClientType() string - GetChainID() string GetClientID() string ValidateBasic() error } @@ -215,7 +213,6 @@ type Header interface { proto.Message ClientType() string - GetChainID() string GetHeight() Height ValidateBasic() error } @@ -245,6 +242,11 @@ type GenesisMetadata interface { GetValue() []byte } +// String returns the string representation of a client status. +func (s Status) String() string { + return string(s) +} + // SelfClient is an interface to create the chains' self client logic type SelfClient interface { ClientType() string @@ -260,8 +262,3 @@ type SelfClient interface { blockHeader []byte, ) (ConsensusState, error) } - -// String returns the string representation of a client status. -func (s Status) String() string { - return string(s) -} diff --git a/modules/light-clients/06-solomachine/types/client_state.go b/modules/light-clients/06-solomachine/types/client_state.go index 844b20b9a20..e489486ed4f 100644 --- a/modules/light-clients/06-solomachine/types/client_state.go +++ b/modules/light-clients/06-solomachine/types/client_state.go @@ -32,11 +32,6 @@ func (cs ClientState) ClientType() string { return exported.Solomachine } -// GetChainID returns the chain-id -func (cs ClientState) GetChainID() string { - return "" -} - // GetLatestHeight returns the latest sequence number. // Return exported.Height to satisfy ClientState interface // Revision number is always 0 for a solo-machine. diff --git a/modules/light-clients/06-solomachine/types/header.go b/modules/light-clients/06-solomachine/types/header.go index 8f305891efa..e22d0d605a9 100644 --- a/modules/light-clients/06-solomachine/types/header.go +++ b/modules/light-clients/06-solomachine/types/header.go @@ -17,11 +17,6 @@ func (Header) ClientType() string { return exported.Solomachine } -// GetChainID returns the chain-id -func (h Header) GetChainID() string { - return "" -} - // GetHeight returns the current sequence number as the height. // Return clientexported.Height to satisfy interface // Revision number is always 0 for a solo-machine diff --git a/modules/light-clients/06-solomachine/types/misbehaviour.go b/modules/light-clients/06-solomachine/types/misbehaviour.go index 852375bcea1..d13b1407be8 100644 --- a/modules/light-clients/06-solomachine/types/misbehaviour.go +++ b/modules/light-clients/06-solomachine/types/misbehaviour.go @@ -17,11 +17,6 @@ func (misbehaviour Misbehaviour) ClientType() string { return exported.Solomachine } -// GetChainID returns the chain-id -func (misbehaviour Misbehaviour) GetChainID() string { - return "" -} - // GetClientID returns the ID of the client that committed a misbehaviour. func (misbehaviour Misbehaviour) GetClientID() string { return misbehaviour.ClientId diff --git a/modules/light-clients/07-tendermint/types/header.go b/modules/light-clients/07-tendermint/types/header.go index d518e6f0799..2da01504710 100644 --- a/modules/light-clients/07-tendermint/types/header.go +++ b/modules/light-clients/07-tendermint/types/header.go @@ -28,11 +28,6 @@ func (h Header) ClientType() string { return exported.Tendermint } -// GetChainID returns the chain-id -func (h Header) GetChainID() string { - return h.Header.ChainID -} - // 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. diff --git a/modules/light-clients/07-tendermint/types/misbehaviour.go b/modules/light-clients/07-tendermint/types/misbehaviour.go index 851f6ebbcce..a15699767f3 100644 --- a/modules/light-clients/07-tendermint/types/misbehaviour.go +++ b/modules/light-clients/07-tendermint/types/misbehaviour.go @@ -31,12 +31,6 @@ func (misbehaviour Misbehaviour) ClientType() string { return exported.Tendermint } -// GetChainID returns the chain-id -func (misbehaviour Misbehaviour) GetChainID() string { - // assuming Header1.hainID and Header2.hainID are same as checked in ValidateBasic - return misbehaviour.Header1.GetChainID() -} - // GetClientID returns the ID of the client that committed a misbehaviour. func (misbehaviour Misbehaviour) GetClientID() string { return misbehaviour.ClientId diff --git a/modules/light-clients/07-tendermint/types/store_test.go b/modules/light-clients/07-tendermint/types/store_test.go index 422cd50fb67..a482fad007e 100644 --- a/modules/light-clients/07-tendermint/types/store_test.go +++ b/modules/light-clients/07-tendermint/types/store_test.go @@ -86,7 +86,7 @@ func (suite *TendermintTestSuite) TestGetProcessedTime() { suite.coordinator.UpdateTime() // coordinator increments time before creating client - expectedTime := suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint).CurrentHeader.Time.Add(ibctesting.TimeIncrement) + expectedTime := suite.chainA.CurrentHeader.Time.Add(ibctesting.TimeIncrement) // Verify ProcessedTime on CreateClient err := path.EndpointA.CreateClient() @@ -102,7 +102,7 @@ func (suite *TendermintTestSuite) TestGetProcessedTime() { suite.coordinator.UpdateTime() // coordinator increments time before updating client - expectedTime = suite.chainA.TestChainClient.(*ibctesting.TestChainTendermint).CurrentHeader.Time.Add(ibctesting.TimeIncrement) + expectedTime = suite.chainA.CurrentHeader.Time.Add(ibctesting.TimeIncrement) // Verify ProcessedTime on UpdateClient err = path.EndpointA.UpdateClient() diff --git a/testing/simapp/test_helpers.go b/testing/simapp/test_helpers.go index 1a33bdf3043..6f0bb2e370b 100644 --- a/testing/simapp/test_helpers.go +++ b/testing/simapp/test_helpers.go @@ -205,7 +205,7 @@ func SetupWithGenesisAccounts(genAccs []authtypes.GenesisAccount, balances ...ba }, ) - app.EndBlock(abci.RequestEndBlock{Height: app.LastBlockHeight()}) + // app.EndBlock(abci.RequestEndBlock{Height: app.LastBlockHeight()}) app.Commit() app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: app.LastBlockHeight() + 1}}) From 37af5a309ad67bf7ee357b166fd2c58f3e248930 Mon Sep 17 00:00:00 2001 From: Michael Tsitrin Date: Wed, 3 May 2023 15:02:03 +0300 Subject: [PATCH 134/140] wrapped 02-client keeper to an interface. added core NewKeeperWithDymint --- modules/core/02-client/abci.go | 2 +- modules/core/02-client/genesis.go | 4 +- modules/core/02-client/keeper/keeper.go | 128 ++++++++++++++++++-- modules/core/02-client/keeper/migrations.go | 14 ++- modules/core/02-client/proposal_handler.go | 2 +- modules/core/keeper/keeper.go | 2 +- modules/core/keeper/migrations.go | 10 +- testing/chain.go | 28 +++++ testing/chain_dymint.go | 14 ++- 9 files changed, 179 insertions(+), 25 deletions(-) diff --git a/modules/core/02-client/abci.go b/modules/core/02-client/abci.go index 9fe681e2276..17d3566f9a4 100644 --- a/modules/core/02-client/abci.go +++ b/modules/core/02-client/abci.go @@ -9,7 +9,7 @@ import ( ) // BeginBlocker updates an existing localhost client with the latest block height. -func BeginBlocker(ctx sdk.Context, k keeper.Keeper) { +func BeginBlocker(ctx sdk.Context, k keeper.KeeperI) { plan, found := k.GetUpgradePlan(ctx) if found { // Once we are at the last block this chain will commit, set the upgraded consensus state diff --git a/modules/core/02-client/genesis.go b/modules/core/02-client/genesis.go index 602b13aba42..b0368766585 100644 --- a/modules/core/02-client/genesis.go +++ b/modules/core/02-client/genesis.go @@ -12,7 +12,7 @@ import ( // InitGenesis initializes the ibc client submodule's state from a provided genesis // state. -func InitGenesis(ctx sdk.Context, k keeper.Keeper, gs types.GenesisState) { +func InitGenesis(ctx sdk.Context, k keeper.KeeperI, gs types.GenesisState) { k.SetParams(ctx, gs.Params) // Set all client metadata first. This will allow client keeper to overwrite client and consensus state keys @@ -54,7 +54,7 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, gs types.GenesisState) { // ExportGenesis returns the ibc client submodule's exported genesis. // NOTE: CreateLocalhost should always be false on export since a // created localhost will be included in the exported clients. -func ExportGenesis(ctx sdk.Context, k keeper.Keeper) types.GenesisState { +func ExportGenesis(ctx sdk.Context, k keeper.KeeperI) types.GenesisState { genClients := k.GetAllGenesisClients(ctx) clientsMetadata, err := k.GetAllClientMetadata(ctx, genClients) if err != nil { diff --git a/modules/core/02-client/keeper/keeper.go b/modules/core/02-client/keeper/keeper.go index 97aab2de87b..ad810f52e46 100644 --- a/modules/core/02-client/keeper/keeper.go +++ b/modules/core/02-client/keeper/keeper.go @@ -1,7 +1,9 @@ package keeper import ( + "context" "fmt" + "reflect" "strings" "github.com/cosmos/cosmos-sdk/codec" @@ -12,15 +14,64 @@ import ( paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/light" "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v5/modules/core/23-commitment/types" host "github.com/cosmos/ibc-go/v5/modules/core/24-host" "github.com/cosmos/ibc-go/v5/modules/core/exported" - ibcdtypes "github.com/cosmos/ibc-go/v5/modules/light-clients/01-dymint/types" + ibctmtypes "github.com/cosmos/ibc-go/v5/modules/light-clients/07-tendermint/types" ) // Keeper represents a type that grants read and write permissions to any client // state information + +type KeeperI interface { + // ClientUnmarshaler interface + MustUnmarshalClientState([]byte) exported.ClientState + MustUnmarshalConsensusState([]byte) exported.ConsensusState + + // ClientState implements the IBC QueryServer interface + ClientState(c context.Context, req *types.QueryClientStateRequest) (*types.QueryClientStateResponse, error) + ClientStates(c context.Context, req *types.QueryClientStatesRequest) (*types.QueryClientStatesResponse, error) + ConsensusState(c context.Context, req *types.QueryConsensusStateRequest) (*types.QueryConsensusStateResponse, error) + ConsensusStates(c context.Context, req *types.QueryConsensusStatesRequest) (*types.QueryConsensusStatesResponse, error) + ConsensusStateHeights(c context.Context, req *types.QueryConsensusStateHeightsRequest) (*types.QueryConsensusStateHeightsResponse, error) + ClientStatus(c context.Context, req *types.QueryClientStatusRequest) (*types.QueryClientStatusResponse, error) + ClientParams(c context.Context, req *types.QueryClientParamsRequest) (*types.QueryClientParamsResponse, error) + UpgradedClientState(c context.Context, req *types.QueryUpgradedClientStateRequest) (*types.QueryUpgradedClientStateResponse, error) + + //From genesis.go + GetAllGenesisClients(ctx sdk.Context) types.IdentifiedClientStates + GetAllClientMetadata(ctx sdk.Context, genClients []types.IdentifiedClientState) ([]types.IdentifiedGenesisMetadata, error) + GetAllConsensusStates(ctx sdk.Context) types.ClientsConsensusStates + GetParams(ctx sdk.Context) types.Params + SetParams(ctx sdk.Context, params types.Params) + GetNextClientSequence(ctx sdk.Context) uint64 + SetAllClientMetadata(ctx sdk.Context, genMetadata []types.IdentifiedGenesisMetadata) + SetClientState(ctx sdk.Context, clientID string, clientState exported.ClientState) + SetClientConsensusState(ctx sdk.Context, clientID string, height exported.Height, consensusState exported.ConsensusState) + SetNextClientSequence(ctx sdk.Context, sequence uint64) + + //msg_server.go + CheckMisbehaviourAndUpdateState(ctx sdk.Context, misbehaviour exported.Misbehaviour) error + UpgradeClient(ctx sdk.Context, clientID string, upgradedClient exported.ClientState, upgradedConsState exported.ConsensusState, proofUpgradeClient []byte, proofUpgradeConsState []byte) error + UpdateClient(ctx sdk.Context, clientID string, header exported.Header) error + CreateClient(ctx sdk.Context, clientState exported.ClientState, consensusState exported.ConsensusState) (string, error) + + //abci.go + GetUpgradedClient(ctx sdk.Context, planHeight int64) ([]byte, bool) + GetUpgradePlan(ctx sdk.Context) (plan upgradetypes.Plan, havePlan bool) + SetUpgradedConsensusState(ctx sdk.Context, planHeight int64, bz []byte) error + GetClientState(ctx sdk.Context, clientID string) (exported.ClientState, bool) + // UpdateClient(ctx sdk.Context, clientID string, header exported.Header) error + MustMarshalConsensusState(consensusState exported.ConsensusState) []byte + + //proposal.go + ClientUpdateProposal(ctx sdk.Context, p *types.ClientUpdateProposal) error + HandleUpgradeProposal(ctx sdk.Context, p *types.UpgradeProposal) error +} + type Keeper struct { storeKey storetypes.StoreKey cdc codec.BinaryCodec @@ -254,26 +305,77 @@ func (k Keeper) GetSelfConsensusState(ctx sdk.Context, height exported.Height) ( return nil, sdkerrors.Wrapf(sdkerrors.ErrNotFound, "no historical info found at height %d", selfHeight.RevisionHeight) } - blockHeader, err := histInfo.Header.Marshal() - if err != nil { - return nil, err + consensusState := &ibctmtypes.ConsensusState{ + Timestamp: histInfo.Header.Time, + Root: commitmenttypes.NewMerkleRoot(histInfo.Header.GetAppHash()), + NextValidatorsHash: histInfo.Header.NextValidatorsHash, } - - //FIXME: refactor this. should be methods of the keeper - sc := ibcdtypes.NewSelfClient() - return sc.GetSelfConsensusStateFromBlocHeader(k.cdc, blockHeader) + return consensusState, nil } // ValidateSelfClient validates the client parameters for a client of the running chain // This function is only used to validate the client state the counterparty stores for this chain // Client must be in same revision as the executing chain func (k Keeper) ValidateSelfClient(ctx sdk.Context, clientState exported.ClientState) error { - if exported.Dymint != clientState.ClientType() { - return sdkerrors.Wrapf(types.ErrInvalidClient, "invalid client type. expected: %s, got: %s", - exported.Dymint, clientState.ClientType()) + tmClient, ok := clientState.(*ibctmtypes.ClientState) + if !ok { + return sdkerrors.Wrapf(types.ErrInvalidClient, "client must be a Tendermint client, expected: %T, got: %T", + &ibctmtypes.ClientState{}, tmClient) + } + + if !tmClient.FrozenHeight.IsZero() { + return types.ErrClientFrozen + } + + if ctx.ChainID() != tmClient.ChainId { + return sdkerrors.Wrapf(types.ErrInvalidClient, "invalid chain-id. expected: %s, got: %s", + ctx.ChainID(), tmClient.ChainId) + } + + revision := types.ParseChainID(ctx.ChainID()) + + // client must be in the same revision as executing chain + if tmClient.LatestHeight.RevisionNumber != revision { + return sdkerrors.Wrapf(types.ErrInvalidClient, "client is not in the same revision as the chain. expected revision: %d, got: %d", + tmClient.LatestHeight.RevisionNumber, revision) + } + + selfHeight := types.NewHeight(revision, uint64(ctx.BlockHeight())) + if tmClient.LatestHeight.GTE(selfHeight) { + return sdkerrors.Wrapf(types.ErrInvalidClient, "client has LatestHeight %d greater than or equal to chain height %d", + tmClient.LatestHeight, selfHeight) + } + + expectedProofSpecs := commitmenttypes.GetSDKSpecs() + if !reflect.DeepEqual(expectedProofSpecs, tmClient.ProofSpecs) { + return sdkerrors.Wrapf(types.ErrInvalidClient, "client has invalid proof specs. expected: %v got: %v", + expectedProofSpecs, tmClient.ProofSpecs) + } + + if err := light.ValidateTrustLevel(tmClient.TrustLevel.ToTendermint()); err != nil { + return sdkerrors.Wrapf(types.ErrInvalidClient, "trust-level invalid: %v", err) + } + + expectedUbdPeriod := k.stakingKeeper.UnbondingTime(ctx) + if expectedUbdPeriod != tmClient.UnbondingPeriod { + return sdkerrors.Wrapf(types.ErrInvalidClient, "invalid unbonding period. expected: %s, got: %s", + expectedUbdPeriod, tmClient.UnbondingPeriod) + } + + if tmClient.UnbondingPeriod < tmClient.TrustingPeriod { + return sdkerrors.Wrapf(types.ErrInvalidClient, "unbonding period must be greater than trusting period. unbonding period (%d) < trusting period (%d)", + tmClient.UnbondingPeriod, tmClient.TrustingPeriod) + } + + if len(tmClient.UpgradePath) != 0 { + // For now, SDK IBC implementation assumes that upgrade path (if defined) is defined by SDK upgrade module + expectedUpgradePath := []string{upgradetypes.StoreKey, upgradetypes.KeyUpgradedIBCState} + if !reflect.DeepEqual(expectedUpgradePath, tmClient.UpgradePath) { + return sdkerrors.Wrapf(types.ErrInvalidClient, "upgrade path must be the upgrade path defined by upgrade module. expected %v, got %v", + expectedUpgradePath, tmClient.UpgradePath) + } } - sc := ibcdtypes.NewSelfClient() - return sc.ValidateSelfClientState(ctx, k.stakingKeeper.UnbondingTime(ctx), clientState) + return nil } // GetUpgradePlan executes the upgrade keeper GetUpgradePlan function. diff --git a/modules/core/02-client/keeper/migrations.go b/modules/core/02-client/keeper/migrations.go index e4ba66760df..01e969ae462 100644 --- a/modules/core/02-client/keeper/migrations.go +++ b/modules/core/02-client/keeper/migrations.go @@ -1,6 +1,8 @@ package keeper import ( + "errors" + sdk "github.com/cosmos/cosmos-sdk/types" v100 "github.com/cosmos/ibc-go/v5/modules/core/02-client/legacy/v100" @@ -8,11 +10,11 @@ import ( // Migrator is a struct for handling in-place store migrations. type Migrator struct { - keeper Keeper + keeper KeeperI } // NewMigrator returns a new Migrator. -func NewMigrator(keeper Keeper) Migrator { +func NewMigrator(keeper KeeperI) Migrator { return Migrator{keeper: keeper} } @@ -23,5 +25,11 @@ func NewMigrator(keeper Keeper) Migrator { // - prunes expired tendermint consensus states // - adds iteration and processed height keys for unexpired tendermint consensus states func (m Migrator) Migrate1to2(ctx sdk.Context) error { - return v100.MigrateStore(ctx, m.keeper.storeKey, m.keeper.cdc) + //FIXME: support different kinds of clients + cl, ok := m.keeper.(Keeper) + if !ok { + return errors.New("client keeper is not a keeper.Keeper") + } + + return v100.MigrateStore(ctx, cl.storeKey, cl.cdc) } diff --git a/modules/core/02-client/proposal_handler.go b/modules/core/02-client/proposal_handler.go index 5064f860ca7..c34e08a9c43 100644 --- a/modules/core/02-client/proposal_handler.go +++ b/modules/core/02-client/proposal_handler.go @@ -10,7 +10,7 @@ import ( ) // NewClientProposalHandler defines the 02-client proposal handler -func NewClientProposalHandler(k keeper.Keeper) govtypes.Handler { +func NewClientProposalHandler(k keeper.KeeperI) govtypes.Handler { return func(ctx sdk.Context, content govtypes.Content) error { switch c := content.(type) { case *types.ClientUpdateProposal: diff --git a/modules/core/keeper/keeper.go b/modules/core/keeper/keeper.go index 65b23fc2339..8d7e2c58519 100644 --- a/modules/core/keeper/keeper.go +++ b/modules/core/keeper/keeper.go @@ -28,7 +28,7 @@ type Keeper struct { cdc codec.BinaryCodec - ClientKeeper clientkeeper.Keeper + ClientKeeper clientkeeper.KeeperI ConnectionKeeper connectionkeeper.Keeper ChannelKeeper channelkeeper.Keeper PortKeeper portkeeper.Keeper diff --git a/modules/core/keeper/migrations.go b/modules/core/keeper/migrations.go index ca56594b113..cbcfd0ea157 100644 --- a/modules/core/keeper/migrations.go +++ b/modules/core/keeper/migrations.go @@ -1,6 +1,8 @@ package keeper import ( + "errors" + sdk "github.com/cosmos/cosmos-sdk/types" clientkeeper "github.com/cosmos/ibc-go/v5/modules/core/02-client/keeper" @@ -23,7 +25,13 @@ func NewMigrator(keeper Keeper) Migrator { // - prunes expired tendermint consensus states // - adds ProcessedHeight and Iteration keys for unexpired tendermint consensus states func (m Migrator) Migrate1to2(ctx sdk.Context) error { - clientMigrator := clientkeeper.NewMigrator(m.keeper.ClientKeeper) + + //FIXME: support different kinds of clients + cl, ok := m.keeper.ClientKeeper.(clientkeeper.Keeper) + if !ok { + return errors.New("client keeper is not a keeper.Keeper") + } + clientMigrator := clientkeeper.NewMigrator(cl) if err := clientMigrator.Migrate1to2(ctx); err != nil { return err } diff --git a/testing/chain.go b/testing/chain.go index 1149224c855..83570439fa0 100644 --- a/testing/chain.go +++ b/testing/chain.go @@ -42,6 +42,32 @@ type SenderAccount struct { SenderAccount authtypes.AccountI } +type TestChainClientI interface { + GetContext() sdk.Context + NextBlock() + BeginBlock() + UpdateCurrentHeaderTime(t time.Time) + ClientConfigToState(ClientConfig ClientConfig) exported.ClientState + GetConsensusState() exported.ConsensusState + NewConfig() ClientConfig + GetSelfClientType() string + GetLastHeader() interface{} +} + +func NewTestChainClient(chain *TestChain, chainConsensusType string) TestChainClientI { + // set the last header to the current header + // use nil trusted fields + switch chainConsensusType { + // case exported.Tendermint: + // return NewChainTendermintClient(chain) + case exported.Dymint: + return NewChainDymintClient(chain) + default: + panic(fmt.Sprintf("client type %s is not supported", chainConsensusType)) + } + +} + // 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 @@ -74,6 +100,8 @@ type TestChain struct { SenderAccount authtypes.AccountI SenderAccounts []SenderAccount + + TestChainClient TestChainClientI } // NewTestChainWithValSet initializes a new TestChain instance with the given validator set diff --git a/testing/chain_dymint.go b/testing/chain_dymint.go index c27fbe27ee1..4aa5caaeaa0 100644 --- a/testing/chain_dymint.go +++ b/testing/chain_dymint.go @@ -148,7 +148,7 @@ func (chain *TestChainDymint) CurrentDMClientHeader() *ibcdmtypes.Header { // CreateDMClientHeader creates a DM header to update the DM client. Args are passed in to allow // caller flexibility to use params that differ from the chain. -func (chain *TestChainDymint) CreateDMClientHeader(chainID string, blockHeight int64, trustedHeight clienttypes.Height, timestamp time.Time, tmValSet, tmTrustedVals *tmtypes.ValidatorSet, signers []tmtypes.PrivValidator) *ibcdmtypes.Header { +func (chain *TestChainDymint) CreateDMClientHeader(chainID string, blockHeight int64, trustedHeight clienttypes.Height, timestamp time.Time, tmValSet, tmTrustedVals *tmtypes.ValidatorSet, signers map[string]tmtypes.PrivValidator) *ibcdmtypes.Header { var ( valSet *tmproto.ValidatorSet trustedVals *tmproto.ValidatorSet @@ -178,7 +178,15 @@ func (chain *TestChainDymint) CreateDMClientHeader(chainID string, blockHeight i blockID := MakeBlockID(hhash, 3, tmhash.Sum([]byte("part_set"))) voteSet := tmtypes.NewVoteSet(chainID, blockHeight, 1, tmproto.PrecommitType, tmValSet) - commit, err := tmtypes.MakeCommit(blockID, blockHeight, 1, voteSet, signers, timestamp) + // MakeCommit 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. + var signerArr []tmtypes.PrivValidator //nolint:prealloc // using prealloc here would be needlessly complex + for _, v := range tmValSet.Validators { //nolint:staticcheck // need to check for nil validator set + signerArr = append(signerArr, signers[v.Address.String()]) + } + + commit, err := tmtypes.MakeCommit(blockID, blockHeight, 1, voteSet, signerArr, timestamp) require.NoError(chain.TC.T, err) signedHeader := &tmproto.SignedHeader{ @@ -187,7 +195,7 @@ func (chain *TestChainDymint) CreateDMClientHeader(chainID string, blockHeight i } // only one sequencer can sign - pv, ok := signers[0].(mock.PV) + pv, ok := signerArr[0].(mock.PV) require.True(chain.TC.T, ok) headerBytes, err := tmHeader.ToProto().Marshal() require.NoError(chain.TC.T, err) From 2b6ccef4755cd618fd5942303e7a8c0a15dd4fa5 Mon Sep 17 00:00:00 2001 From: Michael Tsitrin Date: Wed, 3 May 2023 16:50:34 +0300 Subject: [PATCH 135/140] added dymint keepers --- .../core/02-client/keeper/keeper_dymint.go | 71 +++++++++++++++++++ modules/core/keeper/keeper_dymint.go | 60 ++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 modules/core/02-client/keeper/keeper_dymint.go create mode 100644 modules/core/keeper/keeper_dymint.go diff --git a/modules/core/02-client/keeper/keeper_dymint.go b/modules/core/02-client/keeper/keeper_dymint.go new file mode 100644 index 00000000000..10029f29722 --- /dev/null +++ b/modules/core/02-client/keeper/keeper_dymint.go @@ -0,0 +1,71 @@ +package keeper + +import ( + "github.com/cosmos/cosmos-sdk/codec" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + + "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v5/modules/core/exported" + ibcdtypes "github.com/cosmos/ibc-go/v5/modules/light-clients/01-dymint/types" +) + +// Keeper represents a type that grants read and write permissions to any client +// state information +type DymintKeeper struct { + Keeper +} + +// NewKeeper creates a new NewKeeper instance +func NewDymintKeeper(cdc codec.BinaryCodec, key storetypes.StoreKey, paramSpace paramtypes.Subspace, sk types.StakingKeeper, uk types.UpgradeKeeper) DymintKeeper { + // set KeyTable if it has not already been set + if !paramSpace.HasKeyTable() { + paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable()) + } + + return DymintKeeper{ + Keeper: NewKeeper(cdc, key, paramSpace, sk, uk), + } +} + +// GetSelfConsensusState introspects the (self) past historical info at a given height +// and returns the expected consensus state at that height. +// For now, can only retrieve self consensus states for the current revision +func (k DymintKeeper) GetSelfConsensusState(ctx sdk.Context, height exported.Height) (exported.ConsensusState, error) { + selfHeight, ok := height.(types.Height) + if !ok { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "expected %T, got %T", types.Height{}, height) + } + // check that height revision matches chainID revision + revision := types.ParseChainID(ctx.ChainID()) + if revision != height.GetRevisionNumber() { + return nil, sdkerrors.Wrapf(types.ErrInvalidHeight, "chainID revision number does not match height revision number: expected %d, got %d", revision, height.GetRevisionNumber()) + } + histInfo, found := k.stakingKeeper.GetHistoricalInfo(ctx, int64(selfHeight.RevisionHeight)) + if !found { + return nil, sdkerrors.Wrapf(sdkerrors.ErrNotFound, "no historical info found at height %d", selfHeight.RevisionHeight) + } + + blockHeader, err := histInfo.Header.Marshal() + if err != nil { + return nil, err + } + + //FIXME: refactor this. should be methods of the keeper + sc := ibcdtypes.NewSelfClient() + return sc.GetSelfConsensusStateFromBlocHeader(k.cdc, blockHeader) +} + +// ValidateSelfClient validates the client parameters for a client of the running chain +// This function is only used to validate the client state the counterparty stores for this chain +// Client must be in same revision as the executing chain +func (k DymintKeeper) ValidateSelfClient(ctx sdk.Context, clientState exported.ClientState) error { + if exported.Dymint != clientState.ClientType() { + return sdkerrors.Wrapf(types.ErrInvalidClient, "invalid client type. expected: %s, got: %s", + exported.Dymint, clientState.ClientType()) + } + sc := ibcdtypes.NewSelfClient() + return sc.ValidateSelfClientState(ctx, k.stakingKeeper.UnbondingTime(ctx), clientState) +} diff --git a/modules/core/keeper/keeper_dymint.go b/modules/core/keeper/keeper_dymint.go new file mode 100644 index 00000000000..9f28877a6cc --- /dev/null +++ b/modules/core/keeper/keeper_dymint.go @@ -0,0 +1,60 @@ +package keeper + +import ( + "fmt" + "reflect" + + "github.com/cosmos/cosmos-sdk/codec" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + + clientkeeper "github.com/cosmos/ibc-go/v5/modules/core/02-client/keeper" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + connectionkeeper "github.com/cosmos/ibc-go/v5/modules/core/03-connection/keeper" + connectiontypes "github.com/cosmos/ibc-go/v5/modules/core/03-connection/types" + channelkeeper "github.com/cosmos/ibc-go/v5/modules/core/04-channel/keeper" + portkeeper "github.com/cosmos/ibc-go/v5/modules/core/05-port/keeper" +) + +// NewKeeper creates a new ibc Keeper +func NewKeeperWithDymint( + cdc codec.BinaryCodec, key storetypes.StoreKey, paramSpace paramtypes.Subspace, + stakingKeeper clienttypes.StakingKeeper, upgradeKeeper clienttypes.UpgradeKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, +) *Keeper { + + // register paramSpace at top level keeper + // set KeyTable if it has not already been set + if !paramSpace.HasKeyTable() { + keyTable := clienttypes.ParamKeyTable() + keyTable.RegisterParamSet(&connectiontypes.Params{}) + paramSpace = paramSpace.WithKeyTable(keyTable) + } + + // panic if any of the keepers passed in is empty + if reflect.ValueOf(stakingKeeper).IsZero() { + panic(fmt.Errorf("cannot initialize IBC keeper: empty staking keeper")) + } + + if reflect.ValueOf(upgradeKeeper).IsZero() { + panic(fmt.Errorf("cannot initialize IBC keeper: empty upgrade keeper")) + } + + if reflect.DeepEqual(capabilitykeeper.ScopedKeeper{}, scopedKeeper) { + panic(fmt.Errorf("cannot initialize IBC keeper: empty scoped keeper")) + } + + clientKeeper := clientkeeper.NewDymintKeeper(cdc, key, paramSpace, stakingKeeper, upgradeKeeper) + connectionKeeper := connectionkeeper.NewKeeper(cdc, key, paramSpace, clientKeeper) + portKeeper := portkeeper.NewKeeper(scopedKeeper) + channelKeeper := channelkeeper.NewKeeper(cdc, key, clientKeeper, connectionKeeper, portKeeper, scopedKeeper) + + return &Keeper{ + cdc: cdc, + ClientKeeper: clientKeeper, + ConnectionKeeper: connectionKeeper, + ChannelKeeper: channelKeeper, + PortKeeper: portKeeper, + } +} From 657cfa8e4892672b0d6dd089e5ba6ed9144fda06 Mon Sep 17 00:00:00 2001 From: Michael Tsitrin Date: Wed, 3 May 2023 17:09:23 +0300 Subject: [PATCH 136/140] original tests compiles and run --- modules/core/02-client/keeper/keeper.go | 19 ++ modules/core/02-client/keeper/keeper_test.go | 4 +- testing/chain_tendermint.go | 288 ------------------- 3 files changed, 21 insertions(+), 290 deletions(-) delete mode 100644 testing/chain_tendermint.go diff --git a/modules/core/02-client/keeper/keeper.go b/modules/core/02-client/keeper/keeper.go index ad810f52e46..7447764c182 100644 --- a/modules/core/02-client/keeper/keeper.go +++ b/modules/core/02-client/keeper/keeper.go @@ -26,7 +26,20 @@ import ( // Keeper represents a type that grants read and write permissions to any client // state information +type KeeperIForTests interface { + CreateClient(ctx sdk.Context, clientState exported.ClientState, consensusState exported.ConsensusState) (string, error) + ClientStore(ctx sdk.Context, clientID string) sdk.KVStore + SetClientConsensusState(ctx sdk.Context, clientID string, height exported.Height, consensusState exported.ConsensusState) + // SetClientState(ctx types.Context, clientID string, clientState exported.ClientState) + GetLatestClientConsensusState(ctx sdk.Context, clientID string) (exported.ConsensusState, bool) +} type KeeperI interface { + KeeperIForTests + + ValidateSelfClient(ctx sdk.Context, clientState exported.ClientState) error + UpgradedConsensusState(context.Context, *types.QueryUpgradedConsensusStateRequest) (*types.QueryUpgradedConsensusStateResponse, error) + GetSelfConsensusState(ctx sdk.Context, height exported.Height) (exported.ConsensusState, error) + // ClientUnmarshaler interface MustUnmarshalClientState([]byte) exported.ClientState MustUnmarshalConsensusState([]byte) exported.ConsensusState @@ -41,6 +54,9 @@ type KeeperI interface { ClientParams(c context.Context, req *types.QueryClientParamsRequest) (*types.QueryClientParamsResponse, error) UpgradedClientState(c context.Context, req *types.QueryUpgradedClientStateRequest) (*types.QueryUpgradedClientStateResponse, error) + // GetClientConsensusState(ctx sdk.Context, clientID string) (connection exported.ConsensusState, found bool) + GetClientConsensusState(ctx sdk.Context, clientID string, height exported.Height) (exported.ConsensusState, bool) + //From genesis.go GetAllGenesisClients(ctx sdk.Context) types.IdentifiedClientStates GetAllClientMetadata(ctx sdk.Context, genClients []types.IdentifiedClientState) ([]types.IdentifiedGenesisMetadata, error) @@ -70,6 +86,9 @@ type KeeperI interface { //proposal.go ClientUpdateProposal(ctx sdk.Context, p *types.ClientUpdateProposal) error HandleUpgradeProposal(ctx sdk.Context, p *types.UpgradeProposal) error + + //testing + MustMarshalClientState(clientState exported.ClientState) []byte } type Keeper struct { diff --git a/modules/core/02-client/keeper/keeper_test.go b/modules/core/02-client/keeper/keeper_test.go index 19694c9fa91..719e6b5bc12 100644 --- a/modules/core/02-client/keeper/keeper_test.go +++ b/modules/core/02-client/keeper/keeper_test.go @@ -57,7 +57,7 @@ type KeeperTestSuite struct { cdc codec.Codec ctx sdk.Context - keeper *keeper.Keeper + keeper keeper.KeeperI consensusState *ibctmtypes.ConsensusState header *ibctmtypes.Header valSet *tmtypes.ValidatorSet @@ -86,7 +86,7 @@ func (suite *KeeperTestSuite) SetupTest() { suite.cdc = app.AppCodec() suite.ctx = app.BaseApp.NewContext(isCheckTx, tmproto.Header{Height: height, ChainID: testClientID, Time: now2}) - suite.keeper = &app.IBCKeeper.ClientKeeper + suite.keeper = app.IBCKeeper.ClientKeeper suite.privVal = ibctestingmock.NewPV() pubKey, err := suite.privVal.GetPubKey() diff --git a/testing/chain_tendermint.go b/testing/chain_tendermint.go deleted file mode 100644 index 2bbfcc12bad..00000000000 --- a/testing/chain_tendermint.go +++ /dev/null @@ -1,288 +0,0 @@ -package ibctesting - -import ( - "bytes" - "time" - - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/stretchr/testify/require" - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto/tmhash" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - tmprotoversion "github.com/tendermint/tendermint/proto/tendermint/version" - tmtypes "github.com/tendermint/tendermint/types" - tmversion "github.com/tendermint/tendermint/version" - - clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" - commitmenttypes "github.com/cosmos/ibc-go/v5/modules/core/23-commitment/types" - "github.com/cosmos/ibc-go/v5/modules/core/exported" - ibctmtypes "github.com/cosmos/ibc-go/v5/modules/light-clients/07-tendermint/types" -) - -type TendermintConfig struct { - TrustLevel ibctmtypes.Fraction - TrustingPeriod time.Duration - UnbondingPeriod time.Duration - MaxClockDrift time.Duration - AllowUpdateAfterExpiry bool - AllowUpdateAfterMisbehaviour bool -} - -func (tmcfg *TendermintConfig) GetClientType() string { - return exported.Tendermint -} - -var _ ClientConfig = &TendermintConfig{} - -// TestChainTendermint is a testing struct that 'wraps' a TestChain with the last TM Header, -// the current ABCI header. -type TestChainTendermint struct { - TC *TestChain - - LastHeader *ibctmtypes.Header // header for last block height committed - CurrentHeader tmproto.Header // header for current block height - -} - -var _ TestChainClientI = &TestChainTendermint{} - -// NewChainTendermintClient initializes the consunsus spesisifc pare of the TestChain -func NewChainTendermintClient(tc *TestChain) *TestChainTendermint { - - // create current header and call begin block - header := tmproto.Header{ - ChainID: tc.ChainID, - Height: 1, - Time: tc.Coordinator.CurrentTime.UTC(), - } - - // create an account to send transactions from - chain := &TestChainTendermint{ - tc, - nil, - header, - } - - return chain -} - -func (chain *TestChainTendermint) GetSelfClientType() string { - return exported.Tendermint -} - -func (chain *TestChainTendermint) NewConfig() ClientConfig { - return &TendermintConfig{ - TrustLevel: DefaultTrustLevel, - TrustingPeriod: TrustingPeriod, - UnbondingPeriod: UnbondingPeriod, - MaxClockDrift: MaxClockDrift, - AllowUpdateAfterExpiry: false, - AllowUpdateAfterMisbehaviour: false, - } -} - -// GetContext returns the current context for the application. -func (chain *TestChainTendermint) GetContext() sdk.Context { - return chain.TC.App.GetBaseApp().NewContext(false, chain.CurrentHeader) -} - -// 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. -// -// CONTRACT: this function must only be called after app.Commit() occurs -func (chain *TestChainTendermint) NextBlock() { - // set the last header to the current header - // use nil trusted fields - chain.LastHeader = chain.CurrentTMClientHeader() - - // increment the current header - chain.CurrentHeader = tmproto.Header{ - ChainID: chain.TC.ChainID, - Height: chain.TC.App.LastBlockHeight() + 1, - AppHash: chain.TC.App.LastCommitID().Hash, - // NOTE: the time is increased by the coordinator to maintain time synchrony amongst - // chains. - Time: chain.CurrentHeader.Time, - ValidatorsHash: chain.TC.Vals.Hash(), - NextValidatorsHash: chain.TC.Vals.Hash(), - } - - chain.BeginBlock() -} - -// ConstructUpdateTMClientHeader will construct a valid 07-tendermint Header to update the -// light client on the source chain. -func (chain *TestChainTendermint) ConstructUpdateTMClientHeader(counterparty *TestChain, clientID string) (*ibctmtypes.Header, error) { - // Relayer must query for LatestHeight on client to get TrustedHeight if the trusted height is not set - trustedHeight := chain.TC.GetClientState(clientID).GetLatestHeight().(clienttypes.Height) - return ConstructUpdateTMClientHeaderWithTrustedHeight(counterparty, clientID, trustedHeight) -} - -// ConstructUpdateTMClientHeader will construct a valid 07-tendermint Header to update the -// light client on the source chain. -func ConstructUpdateTMClientHeaderWithTrustedHeight(counterparty *TestChain, clientID string, trustedHeight clienttypes.Height) (*ibctmtypes.Header, error) { - header := counterparty.TestChainClient.GetLastHeader().(*ibctmtypes.Header) - // Relayer must query for LatestHeight on client to get TrustedHeight if the trusted height is not set - require.False(counterparty.T, trustedHeight.IsZero()) - - var ( - tmTrustedVals *tmtypes.ValidatorSet - ok bool - ) - // Once we get TrustedHeight from client, we must query the validators from the counterparty chain - // If the LatestHeight == LastHeader.Height, then TrustedValidators are current validators - // If LatestHeight < LastHeader.Height, we can query the historical validator set from HistoricalInfo - if trustedHeight == header.GetHeight() { - tmTrustedVals = counterparty.Vals - } else { - // NOTE: We need to get validators from counterparty at height: trustedHeight+1 - // since the last trusted validators for a header at height h - // is the NextValidators at h+1 committed to in header h by - // NextValidatorsHash - tmTrustedVals, ok = counterparty.GetValsAtHeight(int64(trustedHeight.RevisionHeight + 1)) - if !ok { - return nil, sdkerrors.Wrapf(ibctmtypes.ErrInvalidHeaderHeight, "could not retrieve trusted validators at trustedHeight: %d", trustedHeight) - } - } - // inject trusted fields into last header - // for now assume revision number is 0 - header.TrustedHeight = trustedHeight - - trustedVals, err := tmTrustedVals.ToProto() - if err != nil { - return nil, err - } - header.TrustedValidators = trustedVals - - return header, nil -} - -// 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 *TestChainTendermint) CurrentTMClientHeader() *ibctmtypes.Header { - return chain.CreateTMClientHeader(chain.TC.ChainID, chain.CurrentHeader.Height, clienttypes.Height{}, chain.CurrentHeader.Time, chain.TC.Vals, nil, chain.TC.Signers) -} - -// 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 *TestChainTendermint) CreateTMClientHeader(chainID string, blockHeight int64, trustedHeight clienttypes.Height, timestamp time.Time, tmValSet, tmTrustedVals *tmtypes.ValidatorSet, signers []tmtypes.PrivValidator) *ibctmtypes.Header { - var ( - valSet *tmproto.ValidatorSet - trustedVals *tmproto.ValidatorSet - ) - require.NotNil(chain.TC.T, tmValSet) - - vsetHash := tmValSet.Hash() - - tmHeader := tmtypes.Header{ - Version: tmprotoversion.Consensus{Block: tmversion.BlockProtocol, App: 2}, - ChainID: chainID, - Height: blockHeight, - Time: timestamp, - LastBlockID: MakeBlockID(make([]byte, tmhash.Size), 10_000, make([]byte, tmhash.Size)), - LastCommitHash: chain.TC.App.LastCommitID().Hash, - DataHash: tmhash.Sum([]byte("data_hash")), - ValidatorsHash: vsetHash, - NextValidatorsHash: vsetHash, - ConsensusHash: tmhash.Sum([]byte("consensus_hash")), - AppHash: chain.CurrentHeader.AppHash, - LastResultsHash: tmhash.Sum([]byte("last_results_hash")), - EvidenceHash: tmhash.Sum([]byte("evidence_hash")), - ProposerAddress: tmValSet.Proposer.Address, //nolint:staticcheck - } - - hhash := tmHeader.Hash() - blockID := MakeBlockID(hhash, 3, tmhash.Sum([]byte("part_set"))) - voteSet := tmtypes.NewVoteSet(chainID, blockHeight, 1, tmproto.PrecommitType, tmValSet) - - commit, err := tmtypes.MakeCommit(blockID, blockHeight, 1, voteSet, signers, timestamp) - require.NoError(chain.TC.T, err) - - signedHeader := &tmproto.SignedHeader{ - Header: tmHeader.ToProto(), - Commit: commit.ToProto(), - } - - if tmValSet != nil { - valSet, err = tmValSet.ToProto() - require.NoError(chain.TC.T, err) - } - - if tmTrustedVals != nil { - trustedVals, err = tmTrustedVals.ToProto() - require.NoError(chain.TC.T, err) - } - - // 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 &ibctmtypes.Header{ - SignedHeader: signedHeader, - ValidatorSet: valSet, - TrustedHeight: trustedHeight, - TrustedValidators: trustedVals, - } -} - -// MakeBlockID copied unimported test functions from tmtypes to use them here -func MakeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) tmtypes.BlockID { - return tmtypes.BlockID{ - Hash: hash, - PartSetHeader: tmtypes.PartSetHeader{ - Total: partSetSize, - Hash: partSetHash, - }, - } -} - -// CreateSortedSignerArray takes two PrivValidators, and the corresponding Validator structs -// (including voting power). It returns a signer array of PrivValidators that matches the -// sorting of ValidatorSet. -// The sorting is first by .VotingPower (descending), with secondary index of .Address (ascending). -func CreateSortedSignerArray(altPrivVal, suitePrivVal tmtypes.PrivValidator, - altVal, suiteVal *tmtypes.Validator, -) []tmtypes.PrivValidator { - switch { - case altVal.VotingPower > suiteVal.VotingPower: - return []tmtypes.PrivValidator{altPrivVal, suitePrivVal} - case altVal.VotingPower < suiteVal.VotingPower: - return []tmtypes.PrivValidator{suitePrivVal, altPrivVal} - default: - if bytes.Compare(altVal.Address, suiteVal.Address) == -1 { - return []tmtypes.PrivValidator{altPrivVal, suitePrivVal} - } - return []tmtypes.PrivValidator{suitePrivVal, altPrivVal} - } -} - -// UpdateTimeForChain updates the clock for this chain. -func (chain *TestChainTendermint) UpdateCurrentHeaderTime(t time.Time) { - chain.CurrentHeader.Time = t -} - -// BeginBlock signals the beginning of a block with chain.CurrentHeader -func (chain *TestChainTendermint) BeginBlock() { - chain.TC.App.BeginBlock(abci.RequestBeginBlock{Header: chain.CurrentHeader}) -} - -// ClientConfigToState builds the ClientState based on the clientConfig and last header -func (chain *TestChainTendermint) ClientConfigToState(clientConfig ClientConfig) exported.ClientState { - tmConfig, ok := clientConfig.(*TendermintConfig) - require.True(chain.TC.T, ok) - - height := chain.LastHeader.GetHeight().(clienttypes.Height) - clientState := ibctmtypes.NewClientState( - chain.TC.ChainID, tmConfig.TrustLevel, tmConfig.TrustingPeriod, tmConfig.UnbondingPeriod, tmConfig.MaxClockDrift, - height, commitmenttypes.GetSDKSpecs(), UpgradePath, tmConfig.AllowUpdateAfterExpiry, tmConfig.AllowUpdateAfterMisbehaviour, - ) - return clientState -} - -// GetConsensusState returns the consensus state of the last header -func (chain *TestChainTendermint) GetConsensusState() exported.ConsensusState { - return chain.LastHeader.ConsensusState() -} - -func (chain *TestChainTendermint) GetLastHeader() interface{} { - return chain.LastHeader -} From 2a4f3301fc7e74c936817ec0517c47a56589c234 Mon Sep 17 00:00:00 2001 From: Michael Tsitrin Date: Wed, 3 May 2023 17:46:41 +0300 Subject: [PATCH 137/140] Trying to make dymint tests work. WIP --- .../01-dymint/types/dymint_test.go | 16 +- .../01-dymint/types/misbehaviour_test.go | 9 +- testing/app.go | 7 + testing/chain.go | 69 ++- testing/chain_dymint.go | 73 ++- testing/coordinator.go | 48 ++ testing/simapp/app_dymint.go | 485 ++++++++++++++++++ 7 files changed, 647 insertions(+), 60 deletions(-) create mode 100644 testing/simapp/app_dymint.go diff --git a/modules/light-clients/01-dymint/types/dymint_test.go b/modules/light-clients/01-dymint/types/dymint_test.go index fcac1dd6ce8..ee1990b8df3 100644 --- a/modules/light-clients/01-dymint/types/dymint_test.go +++ b/modules/light-clients/01-dymint/types/dymint_test.go @@ -49,6 +49,7 @@ type DymintTestSuite struct { cdc codec.Codec privVal tmtypes.PrivValidator valSet *tmtypes.ValidatorSet + signers map[string]tmtypes.PrivValidator valsHash tmbytes.HexBytes header *ibctmtypes.Header now time.Time @@ -66,6 +67,7 @@ func (suite *DymintTestSuite) SetupTest() { } func (suite *DymintTestSuite) SetupTestWithConsensusType(chainAConsensusType string, chainBConsensusType string) { + //FIXME suite.Require().True(chainAConsensusType == exported.Dymint || chainBConsensusType == exported.Dymint) suite.Require().True(chainAConsensusType == exported.Dymint || chainAConsensusType == exported.Tendermint) suite.Require().True(chainBConsensusType == exported.Dymint || chainBConsensusType == exported.Tendermint) @@ -98,15 +100,18 @@ func (suite *DymintTestSuite) SetupTestWithConsensusType(chainAConsensusType str heightMinus1 := clienttypes.NewHeight(0, height.RevisionHeight-1) val := tmtypes.NewValidator(pubKey, 10) + suite.signers = make(map[string]tmtypes.PrivValidator) + suite.signers[val.Address.String()] = suite.privVal + suite.valSet = tmtypes.NewValidatorSet([]*tmtypes.Validator{val}) suite.valsHash = suite.valSet.Hash() if chainAConsensusType == exported.Tendermint { chainBDymint := suite.chainB.TestChainClient.(*ibctesting.TestChainDymint) - suite.header = chainBDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, []tmtypes.PrivValidator{suite.privVal}) + suite.header = chainBDymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, suite.signers) } else { // chainA must be Dymint chainADymint := suite.chainA.TestChainClient.(*ibctesting.TestChainDymint) - suite.header = chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, []tmtypes.PrivValidator{suite.privVal}) + suite.header = chainADymint.CreateDMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, suite.signers) } suite.ctx = app.BaseApp.NewContext(checkTx, tmproto.Header{Height: 1, Time: suite.now}) @@ -116,12 +121,15 @@ func getSuiteSigners(suite *DymintTestSuite) []tmtypes.PrivValidator { return []tmtypes.PrivValidator{suite.privVal} } -func getBothSigners(suite *DymintTestSuite, altVal *tmtypes.Validator, altPrivVal tmtypes.PrivValidator) (*tmtypes.ValidatorSet, []tmtypes.PrivValidator) { +func getBothSigners(suite *DymintTestSuite, altVal *tmtypes.Validator, altPrivVal tmtypes.PrivValidator) (*tmtypes.ValidatorSet, map[string]tmtypes.PrivValidator) { // Create bothValSet with both suite validator and altVal. Would be valid update bothValSet := tmtypes.NewValidatorSet(append(suite.valSet.Validators, altVal)) // Create signer array and ensure it is in same order as bothValSet _, suiteVal := suite.valSet.GetByIndex(0) - bothSigners := ibctesting.CreateSortedSignerArray(altPrivVal, suite.privVal, altVal, suiteVal) + bothSigners := map[string]tmtypes.PrivValidator{ + suiteVal.Address.String(): suite.privVal, + altVal.Address.String(): altPrivVal, + } return bothValSet, bothSigners } diff --git a/modules/light-clients/01-dymint/types/misbehaviour_test.go b/modules/light-clients/01-dymint/types/misbehaviour_test.go index 63f4f605347..b9ecbff8463 100644 --- a/modules/light-clients/01-dymint/types/misbehaviour_test.go +++ b/modules/light-clients/01-dymint/types/misbehaviour_test.go @@ -16,7 +16,8 @@ import ( func (suite *DymintTestSuite) TestMisbehaviour() { var dymintChain *ibctesting.TestChainDymint - signers := []tmtypes.PrivValidator{suite.privVal} + signers := make(map[string]tmtypes.PrivValidator) + signers[val.Address.String()] = privVal heightMinus1 := clienttypes.NewHeight(0, height.RevisionHeight-1) if suite.chainA.TestChainClient.GetSelfClientType() == exported.Dymint { @@ -50,13 +51,15 @@ func (suite *DymintTestSuite) TestMisbehaviourValidateBasic() { // Create alternative validator set with only altVal altValSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{altVal}) - signers := []tmtypes.PrivValidator{suite.privVal} + signers := make(map[string]tmtypes.PrivValidator) + signers[altVal.Address.String()] = altPrivVal // Create signer array and ensure it is in same order as bothValSet _, suiteVal := suite.valSet.GetByIndex(0) bothSigners := ibctesting.CreateSortedSignerArray(altPrivVal, suite.privVal, altVal, suiteVal) - altSigners := []tmtypes.PrivValidator{altPrivVal} + altSigners := make(map[string]tmtypes.PrivValidator, 1) + altSigners[altVal.Address.String()] = altPrivVal heightMinus1 := clienttypes.NewHeight(0, height.RevisionHeight-1) diff --git a/testing/app.go b/testing/app.go index 2c41021f504..07070b6d08e 100644 --- a/testing/app.go +++ b/testing/app.go @@ -49,6 +49,13 @@ type TestingApp interface { LastBlockHeight() int64 } +func SetupTestingAppWithDymint() (TestingApp, map[string]json.RawMessage) { + db := dbm.NewMemDB() + encCdc := simapp.MakeTestEncodingConfig() + app := simapp.NewSimAppWithDymint(log.NewNopLogger(), db, nil, true, map[int64]bool{}, simapp.DefaultNodeHome, 5, encCdc, simapp.EmptyAppOptions{}) + return app, simapp.NewDefaultGenesisState(encCdc.Marshaler) +} + func SetupTestingApp() (TestingApp, map[string]json.RawMessage) { db := dbm.NewMemDB() encCdc := simapp.MakeTestEncodingConfig() diff --git a/testing/chain.go b/testing/chain.go index 83570439fa0..9b5eca4840c 100644 --- a/testing/chain.go +++ b/testing/chain.go @@ -46,27 +46,27 @@ type TestChainClientI interface { GetContext() sdk.Context NextBlock() BeginBlock() - UpdateCurrentHeaderTime(t time.Time) - ClientConfigToState(ClientConfig ClientConfig) exported.ClientState + // UpdateCurrentHeaderTime(t time.Time) + // ClientConfigToState(ClientConfig ClientConfig) exported.ClientState GetConsensusState() exported.ConsensusState - NewConfig() ClientConfig + // NewConfig() ClientConfig GetSelfClientType() string - GetLastHeader() interface{} + // GetLastHeader() interface{} } -func NewTestChainClient(chain *TestChain, chainConsensusType string) TestChainClientI { - // set the last header to the current header - // use nil trusted fields - switch chainConsensusType { - // case exported.Tendermint: - // return NewChainTendermintClient(chain) - case exported.Dymint: - return NewChainDymintClient(chain) - default: - panic(fmt.Sprintf("client type %s is not supported", chainConsensusType)) - } +// func NewTestChainClient(chain *TestChain, chainConsensusType string) TestChainClientI { +// // set the last header to the current header +// // use nil trusted fields +// switch chainConsensusType { +// // case exported.Tendermint: +// // return NewChainTendermintClient(chain) +// case exported.Dymint: +// return NewChainDymintClient(chain) +// default: +// panic(fmt.Sprintf("client type %s is not supported", chainConsensusType)) +// } -} +// } // 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 @@ -120,6 +120,10 @@ type TestChain struct { // 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(t *testing.T, coord *Coordinator, chainID string, valSet *tmtypes.ValidatorSet, signers map[string]tmtypes.PrivValidator) *TestChain { + return newTestChainWithValSet(t, coord, chainID, valSet, signers, exported.Tendermint) +} + +func newTestChainWithValSet(t *testing.T, coord *Coordinator, chainID string, valSet *tmtypes.ValidatorSet, signers map[string]tmtypes.PrivValidator, consensusType string) *TestChain { genAccs := []authtypes.GenesisAccount{} genBals := []banktypes.Balance{} senderAccs := []SenderAccount{} @@ -148,6 +152,9 @@ func NewTestChainWithValSet(t *testing.T, coord *Coordinator, chainID string, va senderAccs = append(senderAccs, senderAcc) } + if consensusType == exported.Dymint { + DefaultTestingAppInit = SetupTestingAppWithDymint + } app := SetupWithGenesisValSet(t, valSet, genAccs, chainID, sdk.DefaultPowerReduction, genBals...) // create current header and call begin block @@ -208,6 +215,32 @@ func NewTestChain(t *testing.T, coord *Coordinator, chainID string) *TestChain { return NewTestChainWithValSet(t, coord, chainID, valSet, signersByAddress) } +// 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 NewDymintTestChain(t *testing.T, coord *Coordinator, chainID string) *TestChain { + // generate validators private/public key + var ( + validatorsPerChain = 1 + validators []*tmtypes.Validator + signersByAddress = make(map[string]tmtypes.PrivValidator, validatorsPerChain) + ) + + for i := 0; i < validatorsPerChain; i++ { + privVal := mock.NewPV() + pubKey, err := privVal.GetPubKey() + require.NoError(t, err) + validators = append(validators, tmtypes.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 := tmtypes.NewValidatorSet(validators) + + return NewTestChainWithValSet(t, coord, chainID, valSet, signersByAddress) +} + // GetContext returns the current context for the application. func (chain *TestChain) GetContext() sdk.Context { return chain.App.GetBaseApp().NewContext(false, chain.CurrentHeader) @@ -413,6 +446,10 @@ func (chain *TestChain) GetPrefix() commitmenttypes.MerklePrefix { return commitmenttypes.NewMerklePrefix(chain.App.GetIBCKeeper().ConnectionKeeper.GetCommitmentPrefix().Bytes()) } +func (chain *TestChain) GetSelfClientType() string { + return exported.Tendermint +} + // ConstructUpdateTMClientHeader will construct a valid 07-tendermint Header to update the // light client on the source chain. func (chain *TestChain) ConstructUpdateTMClientHeader(counterparty *TestChain, clientID string) (*ibctmtypes.Header, error) { diff --git a/testing/chain_dymint.go b/testing/chain_dymint.go index 4aa5caaeaa0..402115b54e9 100644 --- a/testing/chain_dymint.go +++ b/testing/chain_dymint.go @@ -4,7 +4,6 @@ import ( "time" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto/tmhash" @@ -103,42 +102,42 @@ func (chain *TestChainDymint) NextBlock() { chain.BeginBlock() } -// ConstructUpdateDMClientHeader will construct a valid 01-dymint Header to update the -// light client on the source chain. -func ConstructUpdateDMClientHeaderWithTrustedHeight(counterparty *TestChain, clientID string, trustedHeight clienttypes.Height) (*ibcdmtypes.Header, error) { - header := counterparty.TestChainClient.GetLastHeader().(*ibcdmtypes.Header) - - var ( - tmTrustedVals *tmtypes.ValidatorSet - ok bool - ) - // Once we get TrustedHeight from client, we must query the validators from the counterparty chain - // If the LatestHeight == LastHeader.Height, then TrustedValidators are current validators - // If LatestHeight < LastHeader.Height, we can query the historical validator set from HistoricalInfo - if trustedHeight == header.GetHeight() { - tmTrustedVals = counterparty.Vals - } else { - // NOTE: We need to get validators from counterparty at height: trustedHeight+1 - // since the last trusted validators for a header at height h - // is the NextValidators at h+1 committed to in header h by - // NextValidatorsHash - tmTrustedVals, ok = counterparty.GetValsAtHeight(int64(trustedHeight.RevisionHeight + 1)) - if !ok { - return nil, sdkerrors.Wrapf(ibcdmtypes.ErrInvalidHeaderHeight, "could not retrieve trusted validators at trustedHeight: %d", trustedHeight) - } - } - // inject trusted fields into last header - // for now assume revision number is 0 - header.TrustedHeight = trustedHeight - - trustedVals, err := tmTrustedVals.ToProto() - if err != nil { - return nil, err - } - header.TrustedValidators = trustedVals - - return header, nil -} +// // ConstructUpdateDMClientHeader will construct a valid 01-dymint Header to update the +// // light client on the source chain. +// func ConstructUpdateDMClientHeaderWithTrustedHeight(counterparty *TestChain, clientID string, trustedHeight clienttypes.Height) (*ibcdmtypes.Header, error) { +// header := counterparty.TestChainClient.GetLastHeader().(*ibcdmtypes.Header) + +// var ( +// tmTrustedVals *tmtypes.ValidatorSet +// ok bool +// ) +// // Once we get TrustedHeight from client, we must query the validators from the counterparty chain +// // If the LatestHeight == LastHeader.Height, then TrustedValidators are current validators +// // If LatestHeight < LastHeader.Height, we can query the historical validator set from HistoricalInfo +// if trustedHeight == header.GetHeight() { +// tmTrustedVals = counterparty.Vals +// } else { +// // NOTE: We need to get validators from counterparty at height: trustedHeight+1 +// // since the last trusted validators for a header at height h +// // is the NextValidators at h+1 committed to in header h by +// // NextValidatorsHash +// tmTrustedVals, ok = counterparty.GetValsAtHeight(int64(trustedHeight.RevisionHeight + 1)) +// if !ok { +// return nil, sdkerrors.Wrapf(ibcdmtypes.ErrInvalidHeaderHeight, "could not retrieve trusted validators at trustedHeight: %d", trustedHeight) +// } +// } +// // inject trusted fields into last header +// // for now assume revision number is 0 +// header.TrustedHeight = trustedHeight + +// trustedVals, err := tmTrustedVals.ToProto() +// if err != nil { +// return nil, err +// } +// header.TrustedValidators = trustedVals + +// return header, nil +// } // CurrentDMClientHeader creates a DM header using the current header parameters // on the chain. The trusted fields in the header are set to nil. diff --git a/testing/coordinator.go b/testing/coordinator.go index f2cc49f9a91..c3f742aa7e0 100644 --- a/testing/coordinator.go +++ b/testing/coordinator.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/cosmos/ibc-go/v5/modules/core/exported" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" ) @@ -25,6 +26,19 @@ type Coordinator struct { Chains map[string]*TestChain } +// UniqueStringLists returns a list without dupicates +func StringSliceRemoveDuplicates(stringSlice []string) []string { + keys := make(map[string]bool) + list := []string{} + for _, entry := range stringSlice { + if _, value := keys[entry]; !value { + keys[entry] = true + list = append(list, entry) + } + } + return list +} + // NewCoordinator initializes Coordinator with N TestChain's func NewCoordinator(t *testing.T, n int) *Coordinator { chains := make(map[string]*TestChain) @@ -42,6 +56,40 @@ func NewCoordinator(t *testing.T, n int) *Coordinator { return coord } +// NewCoordinatorWithConsensusType initializes Coordinator with len(consensusTypes) TestChain's +// where the self client type of chain i is consensusTypes[i] +func NewCoordinatorWithConsensusType(t *testing.T, consensusTypes []string) *Coordinator { + chains := make(map[string]*TestChain) + coord := &Coordinator{ + T: t, + CurrentTime: globalStartTime, + } + + for i, consensusType := range consensusTypes { + chainID := GetChainID(i + 1) + + switch consensusType { + case exported.Dymint: + chains[chainID] = NewDymintTestChain(t, coord, chainID) + case exported.Tendermint: + chains[chainID] = NewTestChain(t, coord, chainID) + default: + return nil + } + + //Change to NewDymintTestChain + // add the consensusTypes to AllowedClients list + // clientKeeper := chains[chainID].App.GetIBCKeeper().ClientKeeper + // params := clientKeeper.GetParams(chains[chainID].GetContext()) + // clientKeeper.SetParams(chains[chainID].GetContext(), clienttypes.NewParams( + // StringSliceRemoveDuplicates(append(params.AllowedClients, consensusTypes...))..., + // )) + } + coord.Chains = chains + + return coord +} + // IncrementTime iterates through all the TestChain's and increments their current header time // by 5 seconds. // diff --git a/testing/simapp/app_dymint.go b/testing/simapp/app_dymint.go new file mode 100644 index 00000000000..75af1cf5c7b --- /dev/null +++ b/testing/simapp/app_dymint.go @@ -0,0 +1,485 @@ +package simapp + +import ( + "io" + + "github.com/cosmos/cosmos-sdk/baseapp" + _ "github.com/cosmos/cosmos-sdk/client/docs/statik" // this is used for serving docs + servertypes "github.com/cosmos/cosmos-sdk/server/types" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/version" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation" + 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" + authz "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/capability" + capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/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/evidence" + evidencekeeper "github.com/cosmos/cosmos-sdk/x/evidence/keeper" + evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" + "github.com/cosmos/cosmos-sdk/x/feegrant" + feegrantkeeper "github.com/cosmos/cosmos-sdk/x/feegrant/keeper" + feegrantmodule "github.com/cosmos/cosmos-sdk/x/feegrant/module" + "github.com/cosmos/cosmos-sdk/x/genutil" + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + "github.com/cosmos/cosmos-sdk/x/gov" + govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" + "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" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + paramproposal "github.com/cosmos/cosmos-sdk/x/params/types/proposal" + "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" + "github.com/cosmos/cosmos-sdk/x/upgrade" + upgradekeeper "github.com/cosmos/cosmos-sdk/x/upgrade/keeper" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + "github.com/spf13/cast" + "github.com/tendermint/tendermint/libs/log" + tmos "github.com/tendermint/tendermint/libs/os" + dbm "github.com/tendermint/tm-db" + + ica "github.com/cosmos/ibc-go/v5/modules/apps/27-interchain-accounts" + icacontroller "github.com/cosmos/ibc-go/v5/modules/apps/27-interchain-accounts/controller" + icacontrollerkeeper "github.com/cosmos/ibc-go/v5/modules/apps/27-interchain-accounts/controller/keeper" + icacontrollertypes "github.com/cosmos/ibc-go/v5/modules/apps/27-interchain-accounts/controller/types" + icahost "github.com/cosmos/ibc-go/v5/modules/apps/27-interchain-accounts/host" + icahostkeeper "github.com/cosmos/ibc-go/v5/modules/apps/27-interchain-accounts/host/keeper" + icahosttypes "github.com/cosmos/ibc-go/v5/modules/apps/27-interchain-accounts/host/types" + icatypes "github.com/cosmos/ibc-go/v5/modules/apps/27-interchain-accounts/types" + ibcfee "github.com/cosmos/ibc-go/v5/modules/apps/29-fee" + ibcfeekeeper "github.com/cosmos/ibc-go/v5/modules/apps/29-fee/keeper" + ibcfeetypes "github.com/cosmos/ibc-go/v5/modules/apps/29-fee/types" + transfer "github.com/cosmos/ibc-go/v5/modules/apps/transfer" + ibctransferkeeper "github.com/cosmos/ibc-go/v5/modules/apps/transfer/keeper" + ibctransfertypes "github.com/cosmos/ibc-go/v5/modules/apps/transfer/types" + ibc "github.com/cosmos/ibc-go/v5/modules/core" + ibcclient "github.com/cosmos/ibc-go/v5/modules/core/02-client" + ibcclienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" + porttypes "github.com/cosmos/ibc-go/v5/modules/core/05-port/types" + ibchost "github.com/cosmos/ibc-go/v5/modules/core/24-host" + ibckeeper "github.com/cosmos/ibc-go/v5/modules/core/keeper" + ibcmock "github.com/cosmos/ibc-go/v5/testing/mock" + simappparams "github.com/cosmos/ibc-go/v5/testing/simapp/params" +) + +// NewSimApp returns a reference to an initialized SimApp. +func NewSimAppWithDymint( + logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, skipUpgradeHeights map[int64]bool, + homePath string, invCheckPeriod uint, encodingConfig simappparams.EncodingConfig, + appOpts servertypes.AppOptions, baseAppOptions ...func(*baseapp.BaseApp), +) *SimApp { + appCodec := encodingConfig.Marshaler + legacyAmino := encodingConfig.Amino + interfaceRegistry := encodingConfig.InterfaceRegistry + + bApp := baseapp.NewBaseApp(appName, logger, db, encodingConfig.TxConfig.TxDecoder(), baseAppOptions...) + bApp.SetCommitMultiStoreTracer(traceStore) + bApp.SetVersion(version.Version) + bApp.SetInterfaceRegistry(interfaceRegistry) + + keys := sdk.NewKVStoreKeys( + authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, + minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey, + govtypes.StoreKey, paramstypes.StoreKey, ibchost.StoreKey, upgradetypes.StoreKey, feegrant.StoreKey, + evidencetypes.StoreKey, ibctransfertypes.StoreKey, icacontrollertypes.StoreKey, icahosttypes.StoreKey, capabilitytypes.StoreKey, + authzkeeper.StoreKey, ibcfeetypes.StoreKey, + ) + tkeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey) + memKeys := sdk.NewMemoryStoreKeys(capabilitytypes.MemStoreKey) + + app := &SimApp{ + BaseApp: bApp, + legacyAmino: legacyAmino, + appCodec: appCodec, + interfaceRegistry: interfaceRegistry, + invCheckPeriod: invCheckPeriod, + keys: keys, + tkeys: tkeys, + memKeys: memKeys, + } + + app.ParamsKeeper = initParamsKeeper(appCodec, legacyAmino, keys[paramstypes.StoreKey], tkeys[paramstypes.TStoreKey]) + + // set the BaseApp's parameter store + bApp.SetParamStore(app.ParamsKeeper.Subspace(baseapp.Paramspace).WithKeyTable(paramstypes.ConsensusParamsKeyTable())) + + // add capability keeper and ScopeToModule for ibc module + app.CapabilityKeeper = capabilitykeeper.NewKeeper(appCodec, keys[capabilitytypes.StoreKey], memKeys[capabilitytypes.MemStoreKey]) + scopedIBCKeeper := app.CapabilityKeeper.ScopeToModule(ibchost.ModuleName) + scopedTransferKeeper := app.CapabilityKeeper.ScopeToModule(ibctransfertypes.ModuleName) + scopedICAControllerKeeper := app.CapabilityKeeper.ScopeToModule(icacontrollertypes.SubModuleName) + scopedICAHostKeeper := app.CapabilityKeeper.ScopeToModule(icahosttypes.SubModuleName) + + // 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. + scopedIBCMockKeeper := app.CapabilityKeeper.ScopeToModule(ibcmock.ModuleName) + scopedFeeMockKeeper := app.CapabilityKeeper.ScopeToModule(MockFeePort) + scopedICAMockKeeper := app.CapabilityKeeper.ScopeToModule(ibcmock.ModuleName + icacontrollertypes.SubModuleName) + + // seal capability keeper after scoping modules + app.CapabilityKeeper.Seal() + + // SDK module keepers + + app.AccountKeeper = authkeeper.NewAccountKeeper( + appCodec, keys[authtypes.StoreKey], app.GetSubspace(authtypes.ModuleName), authtypes.ProtoBaseAccount, maccPerms, sdk.GetConfig().GetBech32AccountAddrPrefix(), + ) + app.BankKeeper = bankkeeper.NewBaseKeeper( + appCodec, keys[banktypes.StoreKey], app.AccountKeeper, app.GetSubspace(banktypes.ModuleName), app.ModuleAccountAddrs(), + ) + stakingKeeper := stakingkeeper.NewKeeper( + appCodec, keys[stakingtypes.StoreKey], app.AccountKeeper, app.BankKeeper, app.GetSubspace(stakingtypes.ModuleName), + ) + app.MintKeeper = mintkeeper.NewKeeper( + appCodec, keys[minttypes.StoreKey], app.GetSubspace(minttypes.ModuleName), &stakingKeeper, + app.AccountKeeper, app.BankKeeper, authtypes.FeeCollectorName, + ) + app.DistrKeeper = distrkeeper.NewKeeper( + appCodec, keys[distrtypes.StoreKey], app.GetSubspace(distrtypes.ModuleName), app.AccountKeeper, app.BankKeeper, + &stakingKeeper, authtypes.FeeCollectorName, + ) + + app.SlashingKeeper = slashingkeeper.NewKeeper( + appCodec, keys[slashingtypes.StoreKey], &stakingKeeper, app.GetSubspace(slashingtypes.ModuleName), + ) + app.CrisisKeeper = crisiskeeper.NewKeeper( + app.GetSubspace(crisistypes.ModuleName), invCheckPeriod, app.BankKeeper, authtypes.FeeCollectorName, + ) + + app.FeeGrantKeeper = feegrantkeeper.NewKeeper(appCodec, keys[feegrant.StoreKey], app.AccountKeeper) + app.UpgradeKeeper = upgradekeeper.NewKeeper(skipUpgradeHeights, keys[upgradetypes.StoreKey], appCodec, homePath, app.BaseApp, 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 = *stakingKeeper.SetHooks( + stakingtypes.NewMultiStakingHooks(app.DistrKeeper.Hooks(), app.SlashingKeeper.Hooks()), + ) + + app.AuthzKeeper = authzkeeper.NewKeeper(keys[authzkeeper.StoreKey], appCodec, app.MsgServiceRouter(), app.AccountKeeper) + + // IBC Keepers + + app.IBCKeeper = ibckeeper.NewKeeperWithDymint( + appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, + ) + + // register the proposal types + govRouter := govv1beta1.NewRouter() + govRouter.AddRoute(govtypes.RouterKey, govv1beta1.ProposalHandler). + AddRoute(paramproposal.RouterKey, params.NewParamChangeProposalHandler(app.ParamsKeeper)). + AddRoute(distrtypes.RouterKey, distr.NewCommunityPoolSpendProposalHandler(app.DistrKeeper)). + AddRoute(upgradetypes.RouterKey, upgrade.NewSoftwareUpgradeProposalHandler(app.UpgradeKeeper)). + AddRoute(ibcclienttypes.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper)) + + govConfig := govtypes.DefaultConfig() + /* + Example of setting gov params: + govConfig.MaxMetadataLen = 10000 + */ + govKeeper := govkeeper.NewKeeper( + appCodec, keys[govtypes.StoreKey], app.GetSubspace(govtypes.ModuleName), app.AccountKeeper, app.BankKeeper, + &stakingKeeper, govRouter, app.MsgServiceRouter(), govConfig, + ) + + app.GovKeeper = *govKeeper.SetHooks( + govtypes.NewMultiGovHooks( + // register the governance hooks + ), + ) + + // IBC Fee Module keeper + app.IBCFeeKeeper = ibcfeekeeper.NewKeeper( + appCodec, keys[ibcfeetypes.StoreKey], app.GetSubspace(ibcfeetypes.ModuleName), + app.IBCKeeper.ChannelKeeper, // may be replaced with IBC middleware + app.IBCKeeper.ChannelKeeper, + &app.IBCKeeper.PortKeeper, app.AccountKeeper, app.BankKeeper, + ) + + // 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(), + ) + + // 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(), + ) + + // Create IBC Router + ibcRouter := porttypes.NewRouter() + + // Middleware Stacks + + // 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, + ) + + // 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(&app.IBCKeeper.PortKeeper) + + // The mock module is used for testing IBC + mockIBCModule := ibcmock.NewIBCModule(&mockModule, ibcmock.NewIBCApp(ibcmock.ModuleName, scopedIBCMockKeeper)) + ibcRouter.AddRoute(ibcmock.ModuleName, mockIBCModule) + + // 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) + + // 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.NewIBCApp("", 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 host, controller & ica auth modules 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(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 Mock IBC Fee module stack for testing + // SendPacket, since it is originating from the application to core IBC: + // mockModule.SendPacket -> fee.SendPacket -> channel.SendPacket + + // OnRecvPacket, message that originates from core IBC and goes down to app, the flow is the otherway + // channel.RecvPacket -> fee.OnRecvPacket -> mockModule.OnRecvPacket + + // OnAcknowledgementPacket as this is where fee's are paid out + // mockModule.OnAcknowledgementPacket -> fee.OnAcknowledgementPacket -> channel.OnAcknowledgementPacket + + // create fee wrapped mock module + feeMockModule := ibcmock.NewIBCModule(&mockModule, ibcmock.NewIBCApp(MockFeePort, scopedFeeMockKeeper)) + app.FeeMockModule = feeMockModule + feeWithMockModule := ibcfee.NewIBCMiddleware(feeMockModule, app.IBCFeeKeeper) + ibcRouter.AddRoute(MockFeePort, feeWithMockModule) + + // Seal the IBC Router + app.IBCKeeper.SetRouter(ibcRouter) + + // create evidence keeper with router + evidenceKeeper := evidencekeeper.NewKeeper( + appCodec, keys[evidencetypes.StoreKey], &app.StakingKeeper, app.SlashingKeeper, + ) + // 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.mm = module.NewManager( + // SDK app modules + genutil.NewAppModule( + app.AccountKeeper, app.StakingKeeper, app.BaseApp.DeliverTx, + encodingConfig.TxConfig, + ), + auth.NewAppModule(appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts), + vesting.NewAppModule(app.AccountKeeper, app.BankKeeper), + bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper), + capability.NewAppModule(appCodec, *app.CapabilityKeeper), + crisis.NewAppModule(&app.CrisisKeeper, skipGenesisInvariants), + feegrantmodule.NewAppModule(appCodec, app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, app.interfaceRegistry), + gov.NewAppModule(appCodec, app.GovKeeper, app.AccountKeeper, app.BankKeeper), + mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper, nil), + slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper), + distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper), + staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper), + upgrade.NewAppModule(app.UpgradeKeeper), + evidence.NewAppModule(app.EvidenceKeeper), + ibc.NewAppModule(app.IBCKeeper), + params.NewAppModule(app.ParamsKeeper), + authzmodule.NewAppModule(appCodec, app.AuthzKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), + + // IBC modules + transfer.NewAppModule(app.TransferKeeper), + ibcfee.NewAppModule(app.IBCFeeKeeper), + ica.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper), + mockModule, + ) + + // 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 + // NOTE: capability module's beginblocker must come before any modules using capabilities (e.g. IBC) + app.mm.SetOrderBeginBlockers( + upgradetypes.ModuleName, capabilitytypes.ModuleName, minttypes.ModuleName, distrtypes.ModuleName, slashingtypes.ModuleName, + evidencetypes.ModuleName, stakingtypes.ModuleName, ibchost.ModuleName, ibctransfertypes.ModuleName, authtypes.ModuleName, + banktypes.ModuleName, govtypes.ModuleName, crisistypes.ModuleName, genutiltypes.ModuleName, authz.ModuleName, feegrant.ModuleName, + paramstypes.ModuleName, vestingtypes.ModuleName, icatypes.ModuleName, ibcfeetypes.ModuleName, ibcmock.ModuleName, + ) + app.mm.SetOrderEndBlockers( + crisistypes.ModuleName, govtypes.ModuleName, stakingtypes.ModuleName, ibchost.ModuleName, ibctransfertypes.ModuleName, + capabilitytypes.ModuleName, authtypes.ModuleName, banktypes.ModuleName, distrtypes.ModuleName, slashingtypes.ModuleName, + minttypes.ModuleName, genutiltypes.ModuleName, evidencetypes.ModuleName, authz.ModuleName, feegrant.ModuleName, paramstypes.ModuleName, + upgradetypes.ModuleName, vestingtypes.ModuleName, icatypes.ModuleName, ibcfeetypes.ModuleName, ibcmock.ModuleName, + ) + + // NOTE: The genutils module must occur after staking so that pools are + // properly initialized with tokens from genesis accounts. + // 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, authtypes.ModuleName, banktypes.ModuleName, distrtypes.ModuleName, stakingtypes.ModuleName, + slashingtypes.ModuleName, govtypes.ModuleName, minttypes.ModuleName, crisistypes.ModuleName, + ibchost.ModuleName, genutiltypes.ModuleName, evidencetypes.ModuleName, authz.ModuleName, ibctransfertypes.ModuleName, + icatypes.ModuleName, ibcfeetypes.ModuleName, ibcmock.ModuleName, feegrant.ModuleName, paramstypes.ModuleName, upgradetypes.ModuleName, vestingtypes.ModuleName, + ) + + app.mm.RegisterInvariants(&app.CrisisKeeper) + app.mm.RegisterRoutes(app.Router(), app.QueryRouter(), encodingConfig.Amino) + app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter()) + app.mm.RegisterServices(app.configurator) + + // add test gRPC service for testing gRPC queries in isolation + testdata.RegisterQueryServer(app.GRPCQueryRouter(), testdata.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 + app.sm = module.NewSimulationManager( + auth.NewAppModule(appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts), + bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper), + capability.NewAppModule(appCodec, *app.CapabilityKeeper), + feegrantmodule.NewAppModule(appCodec, app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, app.interfaceRegistry), + gov.NewAppModule(appCodec, app.GovKeeper, app.AccountKeeper, app.BankKeeper), + mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper, nil), + staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper), + distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper), + slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper), + params.NewAppModule(app.ParamsKeeper), + evidence.NewAppModule(app.EvidenceKeeper), + authzmodule.NewAppModule(appCodec, app.AuthzKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), + ibc.NewAppModule(app.IBCKeeper), + transfer.NewAppModule(app.TransferKeeper), + ica.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper), + ) + + app.sm.RegisterStoreDecoders() + + // initialize stores + app.MountKVStores(keys) + app.MountTransientStores(tkeys) + app.MountMemoryStores(memKeys) + + // initialize BaseApp + app.SetInitChainer(app.InitChainer) + app.SetBeginBlocker(app.BeginBlocker) + anteHandler, err := NewAnteHandler( + HandlerOptions{ + HandlerOptions: ante.HandlerOptions{ + AccountKeeper: app.AccountKeeper, + BankKeeper: app.BankKeeper, + SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), + FeegrantKeeper: app.FeeGrantKeeper, + SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + }, + IBCKeeper: app.IBCKeeper, + }, + ) + if err != nil { + panic(err) + } + + app.SetAnteHandler(anteHandler) + + app.SetEndBlocker(app.EndBlocker) + + app.setupUpgradeHandlers() + + if loadLatest { + if err := app.LoadLatestVersion(); err != nil { + tmos.Exit(err.Error()) + } + } + + app.ScopedIBCKeeper = scopedIBCKeeper + app.ScopedTransferKeeper = scopedTransferKeeper + app.ScopedICAControllerKeeper = scopedICAControllerKeeper + app.ScopedICAHostKeeper = scopedICAHostKeeper + + // NOTE: the IBC mock keeper and application module is used only for testing core IBC. Do + // note replicate if you do not need to test core IBC or light clients. + app.ScopedIBCMockKeeper = scopedIBCMockKeeper + app.ScopedICAMockKeeper = scopedICAMockKeeper + app.ScopedFeeMockKeeper = scopedFeeMockKeeper + + return app +} From 97171e2592bf07658eb141347ebdba6ce942324c Mon Sep 17 00:00:00 2001 From: Michael Tsitrin Date: Thu, 11 May 2023 09:45:33 +0300 Subject: [PATCH 138/140] fixed linter --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- docs/ibc/integration.md | 4 ++-- modules/core/02-client/keeper/keeper.go | 10 +++++----- .../01-dymint/types/misbehaviour_handle.go | 6 ++---- modules/light-clients/01-dymint/types/store.go | 2 +- modules/light-clients/01-dymint/types/update.go | 5 ++++- testing/chain_dymint.go | 6 ++---- testing/coordinator.go | 2 +- 8 files changed, 18 insertions(+), 19 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 4479c1cfc8b..039ea96388b 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -20,7 +20,7 @@ write a little note why. - [ ] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. -- [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/main/docs/docs/building-modules/10-structure.md). +- [ ] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/main/docs/docs/building-modules/11-structure.md). - [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [ ] Updated relevant documentation (`docs/`) or specification (`x//spec/`) - [ ] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). diff --git a/docs/ibc/integration.md b/docs/ibc/integration.md index fba5798eadd..c0bc48c33a3 100644 --- a/docs/ibc/integration.md +++ b/docs/ibc/integration.md @@ -139,7 +139,7 @@ func NewApp(...args) *App { ### 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://github.com/cosmos/cosmos-sdk/blob/main/docs/docs/building-modules/13-simulator.md). +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://github.com/cosmos/cosmos-sdk/blob/main/docs/docs/building-modules/14-simulator.md). ```go // app.go @@ -178,7 +178,7 @@ connection handhake. 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](https://github.com/cosmos/ibc/blob/master/spec/client/ics-009-loopback-client) to connect two +client](https://github.com/cosmos/ibc/tree/main/spec/client/ics-009-loopback-cilent) to connect two different modules from the same chain. ::: tip diff --git a/modules/core/02-client/keeper/keeper.go b/modules/core/02-client/keeper/keeper.go index 7447764c182..e3540dec307 100644 --- a/modules/core/02-client/keeper/keeper.go +++ b/modules/core/02-client/keeper/keeper.go @@ -57,7 +57,7 @@ type KeeperI interface { // GetClientConsensusState(ctx sdk.Context, clientID string) (connection exported.ConsensusState, found bool) GetClientConsensusState(ctx sdk.Context, clientID string, height exported.Height) (exported.ConsensusState, bool) - //From genesis.go + // From genesis.go GetAllGenesisClients(ctx sdk.Context) types.IdentifiedClientStates GetAllClientMetadata(ctx sdk.Context, genClients []types.IdentifiedClientState) ([]types.IdentifiedGenesisMetadata, error) GetAllConsensusStates(ctx sdk.Context) types.ClientsConsensusStates @@ -69,13 +69,13 @@ type KeeperI interface { SetClientConsensusState(ctx sdk.Context, clientID string, height exported.Height, consensusState exported.ConsensusState) SetNextClientSequence(ctx sdk.Context, sequence uint64) - //msg_server.go + // msg_server.go CheckMisbehaviourAndUpdateState(ctx sdk.Context, misbehaviour exported.Misbehaviour) error UpgradeClient(ctx sdk.Context, clientID string, upgradedClient exported.ClientState, upgradedConsState exported.ConsensusState, proofUpgradeClient []byte, proofUpgradeConsState []byte) error UpdateClient(ctx sdk.Context, clientID string, header exported.Header) error CreateClient(ctx sdk.Context, clientState exported.ClientState, consensusState exported.ConsensusState) (string, error) - //abci.go + // abci.go GetUpgradedClient(ctx sdk.Context, planHeight int64) ([]byte, bool) GetUpgradePlan(ctx sdk.Context) (plan upgradetypes.Plan, havePlan bool) SetUpgradedConsensusState(ctx sdk.Context, planHeight int64, bz []byte) error @@ -83,11 +83,11 @@ type KeeperI interface { // UpdateClient(ctx sdk.Context, clientID string, header exported.Header) error MustMarshalConsensusState(consensusState exported.ConsensusState) []byte - //proposal.go + // proposal.go ClientUpdateProposal(ctx sdk.Context, p *types.ClientUpdateProposal) error HandleUpgradeProposal(ctx sdk.Context, p *types.UpgradeProposal) error - //testing + // testing MustMarshalClientState(clientState exported.ClientState) []byte } diff --git a/modules/light-clients/01-dymint/types/misbehaviour_handle.go b/modules/light-clients/01-dymint/types/misbehaviour_handle.go index c102612305f..0e94bf20dd8 100644 --- a/modules/light-clients/01-dymint/types/misbehaviour_handle.go +++ b/modules/light-clients/01-dymint/types/misbehaviour_handle.go @@ -51,12 +51,10 @@ func (cs ClientState) CheckMisbehaviourAndUpdateState( if bytes.Equal(blockID1.Hash, blockID2.Hash) { return nil, sdkerrors.Wrap(clienttypes.ErrInvalidMisbehaviour, "headers block hashes are equal") } - } else { // 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). - if tmMisbehaviour.Header1.SignedHeader.Header.Time.After(tmMisbehaviour.Header2.SignedHeader.Header.Time) { - return nil, sdkerrors.Wrap(clienttypes.ErrInvalidMisbehaviour, "headers are not at same height and are monotonically increasing") - } + } else if tmMisbehaviour.Header1.SignedHeader.Header.Time.After(tmMisbehaviour.Header2.SignedHeader.Header.Time) { + return nil, sdkerrors.Wrap(clienttypes.ErrInvalidMisbehaviour, "headers are not at same height and are monotonically increasing") } // Regardless of the type of misbehaviour, ensure that both headers are valid and would have been accepted by light-client diff --git a/modules/light-clients/01-dymint/types/store.go b/modules/light-clients/01-dymint/types/store.go index 0142acd47d8..b9d5a680199 100644 --- a/modules/light-clients/01-dymint/types/store.go +++ b/modules/light-clients/01-dymint/types/store.go @@ -304,7 +304,7 @@ func PruneAllExpiredConsensusStates( return false } - IterateConsensusStateAscending(clientStore, pruneCb) + err = IterateConsensusStateAscending(clientStore, pruneCb) if err != nil { return err } diff --git a/modules/light-clients/01-dymint/types/update.go b/modules/light-clients/01-dymint/types/update.go index 6c675337379..07a945d0699 100644 --- a/modules/light-clients/01-dymint/types/update.go +++ b/modules/light-clients/01-dymint/types/update.go @@ -128,7 +128,10 @@ func (cs ClientState) CheckHeaderAndUpdateState( } return true } - IterateConsensusStateAscending(clientStore, pruneCb) + err = IterateConsensusStateAscending(clientStore, pruneCb) + if err != nil { + return nil, nil, err + } if pruneError != nil { return nil, nil, pruneError } diff --git a/testing/chain_dymint.go b/testing/chain_dymint.go index 402115b54e9..4234a0c0952 100644 --- a/testing/chain_dymint.go +++ b/testing/chain_dymint.go @@ -204,10 +204,8 @@ func (chain *TestChainDymint) CreateDMClientHeader(chainID string, blockHeight i // Dymint check the header bytes signatures signedHeader.Commit.Signatures[0].Signature = signedBytes - if tmValSet != nil { - valSet, err = tmValSet.ToProto() - require.NoError(chain.TC.T, err) - } + valSet, err = tmValSet.ToProto() + require.NoError(chain.TC.T, err) if tmTrustedVals != nil { trustedVals, err = tmTrustedVals.ToProto() diff --git a/testing/coordinator.go b/testing/coordinator.go index c3f742aa7e0..9ac863f48fe 100644 --- a/testing/coordinator.go +++ b/testing/coordinator.go @@ -77,7 +77,7 @@ func NewCoordinatorWithConsensusType(t *testing.T, consensusTypes []string) *Coo return nil } - //Change to NewDymintTestChain + // Change to NewDymintTestChain // add the consensusTypes to AllowedClients list // clientKeeper := chains[chainID].App.GetIBCKeeper().ClientKeeper // params := clientKeeper.GetParams(chains[chainID].GetContext()) From 483b0edbf03eff7c5ff07dab630f0a4cc4f7d195 Mon Sep 17 00:00:00 2001 From: Michael Tsitrin Date: Thu, 11 May 2023 09:50:29 +0300 Subject: [PATCH 139/140] gofumpt --- .../light-clients/01-dymint/types/client_state_test.go | 4 +--- modules/light-clients/01-dymint/types/dymint_test.go | 4 ++-- modules/light-clients/01-dymint/types/header.go | 1 - .../light-clients/01-dymint/types/proposal_handle_test.go | 4 +--- modules/light-clients/01-dymint/types/update.go | 5 ++--- modules/light-clients/01-dymint/types/upgrade.go | 8 ++++---- modules/light-clients/01-dymint/types/upgrade_test.go | 4 +--- 7 files changed, 11 insertions(+), 19 deletions(-) diff --git a/modules/light-clients/01-dymint/types/client_state_test.go b/modules/light-clients/01-dymint/types/client_state_test.go index 46771571bff..6cfc7b7c704 100644 --- a/modules/light-clients/01-dymint/types/client_state_test.go +++ b/modules/light-clients/01-dymint/types/client_state_test.go @@ -27,9 +27,7 @@ const ( fiftyOneCharChainID = "123456789012345678901234567890123456789012345678901" ) -var ( - invalidProof = []byte("invalid proof") -) +var invalidProof = []byte("invalid proof") func (suite *DymintTestSuite) TestStatus() { var ( diff --git a/modules/light-clients/01-dymint/types/dymint_test.go b/modules/light-clients/01-dymint/types/dymint_test.go index ee1990b8df3..84b7d4947a4 100644 --- a/modules/light-clients/01-dymint/types/dymint_test.go +++ b/modules/light-clients/01-dymint/types/dymint_test.go @@ -62,12 +62,12 @@ type DymintTestSuite struct { } func (suite *DymintTestSuite) SetupTest() { - //suite.SetupTestWithConsensusType(exported.Dymint, exported.Tendermint) + // suite.SetupTestWithConsensusType(exported.Dymint, exported.Tendermint) suite.SetupTestWithConsensusType(suite.chainAConsensusType, suite.chainBConsensusType) } func (suite *DymintTestSuite) SetupTestWithConsensusType(chainAConsensusType string, chainBConsensusType string) { - //FIXME + // FIXME suite.Require().True(chainAConsensusType == exported.Dymint || chainBConsensusType == exported.Dymint) suite.Require().True(chainAConsensusType == exported.Dymint || chainAConsensusType == exported.Tendermint) suite.Require().True(chainBConsensusType == exported.Dymint || chainBConsensusType == exported.Tendermint) diff --git a/modules/light-clients/01-dymint/types/header.go b/modules/light-clients/01-dymint/types/header.go index aedf855365d..bda7c4934b4 100644 --- a/modules/light-clients/01-dymint/types/header.go +++ b/modules/light-clients/01-dymint/types/header.go @@ -131,7 +131,6 @@ func (h Header) ValidateCommit() (err error) { } } else { return fmt.Errorf("proposer is not in the validator set (proposer: %x)", h.Header.ProposerAddress) - } return nil diff --git a/modules/light-clients/01-dymint/types/proposal_handle_test.go b/modules/light-clients/01-dymint/types/proposal_handle_test.go index 22e89d4b597..c237b6337a5 100644 --- a/modules/light-clients/01-dymint/types/proposal_handle_test.go +++ b/modules/light-clients/01-dymint/types/proposal_handle_test.go @@ -30,7 +30,7 @@ func (suite *DymintTestSuite) TestCheckSubstituteUpdateStateBasic() { { "non-matching substitute", func() { suite.coordinator.SetupClients(substitutePath) - //substituteClientState = suite.chainA.GetClientState(substitutePath.EndpointA.ClientID) + // substituteClientState = suite.chainA.GetClientState(substitutePath.EndpointA.ClientID) // switch substituteClientState.ClientType() { // case exported.Dymint: // dmClientState, ok := substituteClientState.(*types.ClientState) @@ -55,7 +55,6 @@ func (suite *DymintTestSuite) TestCheckSubstituteUpdateStateBasic() { tc := tc suite.Run(tc.name, func() { - suite.SetupTest() // reset subjectPath := ibctesting.NewPath(suite.chainA, suite.chainB) substitutePath = ibctesting.NewPath(suite.chainA, suite.chainB) @@ -277,7 +276,6 @@ func (suite *DymintTestSuite) TestCheckSubstituteAndUpdateState() { suite.Require().Error(err) suite.Require().Nil(updatedClient) } - }) } } diff --git a/modules/light-clients/01-dymint/types/update.go b/modules/light-clients/01-dymint/types/update.go index 07a945d0699..cdd6072a92f 100644 --- a/modules/light-clients/01-dymint/types/update.go +++ b/modules/light-clients/01-dymint/types/update.go @@ -150,8 +150,8 @@ func _verifyNewHeaderAndVals( untrustedHeader *tmtypes.SignedHeader, trustingPeriod time.Duration, now time.Time, - maxClockDrift time.Duration) error { - + maxClockDrift time.Duration, +) error { if light.HeaderExpired(trustedHeader, trustingPeriod, now) { return light.ErrOldHeaderExpired{At: trustedHeader.Time.Add(trustingPeriod), Now: now} } @@ -188,7 +188,6 @@ func checkValidity( clientState *ClientState, consState *ConsensusState, header *Header, currentTimestamp time.Time, ) error { - if err := header.ValidateCommit(); err != nil { return err } diff --git a/modules/light-clients/01-dymint/types/upgrade.go b/modules/light-clients/01-dymint/types/upgrade.go index d5b0761b4f7..666cc4de465 100644 --- a/modules/light-clients/01-dymint/types/upgrade.go +++ b/modules/light-clients/01-dymint/types/upgrade.go @@ -20,10 +20,10 @@ import ( // - the upgradedClient is not a Dymint ClientState // - the lastest 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 Dymint chain specified parameter in upgraded client such as ChainID, -// and ProofSpecs do not match parameters set by 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 Dymint chain specified parameter in upgraded client such as ChainID, +// and ProofSpecs do not match parameters set by committed client func (cs ClientState) VerifyUpgradeAndUpdateState( ctx sdk.Context, cdc codec.BinaryCodec, clientStore sdk.KVStore, upgradedClient exported.ClientState, upgradedConsState exported.ConsensusState, diff --git a/modules/light-clients/01-dymint/types/upgrade_test.go b/modules/light-clients/01-dymint/types/upgrade_test.go index d91c5c3c73a..6261f5aa19b 100644 --- a/modules/light-clients/01-dymint/types/upgrade_test.go +++ b/modules/light-clients/01-dymint/types/upgrade_test.go @@ -10,9 +10,7 @@ import ( ibctesting "github.com/cosmos/ibc-go/v5/testing" ) -var ( - newChainId = "newChainId-1" -) +var newChainId = "newChainId-1" func (suite *DymintTestSuite) TestVerifyUpgrade() { var ( From ff99454ab6ab646a583ea71573a65436ccc0b9bf Mon Sep 17 00:00:00 2001 From: Michael Tsitrin Date: Thu, 11 May 2023 09:53:35 +0300 Subject: [PATCH 140/140] fixed goimports --- modules/light-clients/01-dymint/types/self_client.go | 1 + modules/light-clients/07-tendermint/types/self_client.go | 1 + testing/coordinator.go | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/light-clients/01-dymint/types/self_client.go b/modules/light-clients/01-dymint/types/self_client.go index c883a8d248c..85aff8f3b7c 100644 --- a/modules/light-clients/01-dymint/types/self_client.go +++ b/modules/light-clients/01-dymint/types/self_client.go @@ -10,6 +10,7 @@ import ( tmproto "github.com/tendermint/tendermint/proto/tendermint/types" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" commitmenttypes "github.com/cosmos/ibc-go/v5/modules/core/23-commitment/types" "github.com/cosmos/ibc-go/v5/modules/core/exported" diff --git a/modules/light-clients/07-tendermint/types/self_client.go b/modules/light-clients/07-tendermint/types/self_client.go index e7f8bc058e3..b1a0b86f222 100644 --- a/modules/light-clients/07-tendermint/types/self_client.go +++ b/modules/light-clients/07-tendermint/types/self_client.go @@ -11,6 +11,7 @@ import ( tmproto "github.com/tendermint/tendermint/proto/tendermint/types" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" commitmenttypes "github.com/cosmos/ibc-go/v5/modules/core/23-commitment/types" "github.com/cosmos/ibc-go/v5/modules/core/exported" diff --git a/testing/coordinator.go b/testing/coordinator.go index 9ac863f48fe..63f4f2215d8 100644 --- a/testing/coordinator.go +++ b/testing/coordinator.go @@ -6,9 +6,10 @@ import ( "testing" "time" - "github.com/cosmos/ibc-go/v5/modules/core/exported" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" + + "github.com/cosmos/ibc-go/v5/modules/core/exported" ) var (