diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8f76a660095..619a694e09e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -23,6 +23,10 @@ /modules/light-clients/ @colin-axner @AdityaSripal @damiannolan /proto/ibc/lightclients/ @colin-axner @AdityaSripal @damiannolan +# CODEOWNERS for 08-wasm light client module + +/modules/light-clients/08-wasm/ @colin-axner @AdityaSripal @damiannolan @charleenfei @chatton @DimitrisJim @srdtrk + # CODEOWNERS for ICS 20 /modules/apps/transfer/ @colin-axner @AdityaSripal @damiannolan @@ -38,9 +42,11 @@ /modules/apps/29-fee/ @AdityaSripal @charleenfei @colin-axner @damiannolan /proto/ibc/applications/fee/ @AdityaSripal @charleenfei @colin-axner @damiannolan -# CODEOWNERS for docs -/docs/ @colin-axner @AdityaSripal @crodriguezvega @charleenfei @damiannolan @chatton @DimitrisJim @srdtrk - # CODEOWNERS for callbacks middleware /modules/apps/callbacks/ @colin-axner @AdityaSripal @damiannolan @srdtrk + +# CODEOWNERS for docs + +/docs/ @colin-axner @AdityaSripal @crodriguezvega @charleenfei @damiannolan @chatton @DimitrisJim @srdtrk + diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c0e03aef631..7a5496efee1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -28,4 +28,14 @@ updates: interval: daily open-pull-requests-limit: 10 labels: - - dependencies \ No newline at end of file + - dependencies + + - package-ecosystem: gomod + directory: "/modules/light-clients/08-wasm" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - dependencies + + diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c4e0320a26e..36139dec250 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -36,7 +36,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go-arch: ['amd64', 'arm', 'arm64'] + go-arch: ['amd64', 'arm64'] steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v4 diff --git a/.github/workflows/wasm-client.yml b/.github/workflows/wasm-client.yml new file mode 100644 index 00000000000..34c316151b4 --- /dev/null +++ b/.github/workflows/wasm-client.yml @@ -0,0 +1,58 @@ +name: Wasm Light-Client +# This workflow runs when a PR is opened that targets code that is part of the wasm light-client. +on: + pull_request: + paths: + - '.github/workflows/wasm-client.yml' + - 'modules/light-clients/08-wasm/**' + - 'proto/ibc/lightclients/wasm/**' +permissions: + contents: read + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/setup-go@v4 + with: + go-version: '1.21' + - uses: actions/checkout@v3 + - uses: golangci/golangci-lint-action@v3.6.0 + with: + version: v1.54.2 + args: --timeout 10m + working-directory: modules/light-clients/08-wasm + + build: + runs-on: ubuntu-latest + strategy: + matrix: + go-arch: ['amd64', 'arm64'] + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version: '1.21' + # Install cross compiler for ARM64. Export CC env variable. + - name: Install compiler for arm64. + run: | + sudo apt-get update + sudo apt-get install -y gcc-aarch64-linux-gnu + echo "CC=aarch64-linux-gnu-gcc" >> $GITHUB_ENV + if: matrix.go-arch == 'arm64' + - name: Build wasm-client + run: | + cd modules/light-clients/08-wasm + GOARCH=${{ matrix.go-arch }} CGO_ENABLED=1 go build ./... + + tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version: '1.21' + - name: Go Test + run: | + cd modules/light-clients/08-wasm + go test -v -mod=readonly ./... diff --git a/.gitignore b/.gitignore index 38d6c10bc20..e593116b68f 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ artifacts baseapp/data/* client/lcd/keys/* mytestnet +modules/light-clients/08-wasm/**/ibc_08-wasm_client_data/ # Testing coverage.txt @@ -58,9 +59,14 @@ dependency-graph.png *.history +tmp/ +*.wasm # Go go.work go.work.sum +# E2E WASM contract +!ics10_grandpa_cw.wasm + # Python venv diff --git a/Dockerfile b/Dockerfile index c6eaa8e135e..74fd9af413a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,7 +23,7 @@ COPY go.sum . RUN go mod download -RUN BUILD_TAGS=muslc make build +RUN make build FROM alpine:3.18 ARG IBC_GO_VERSION @@ -33,4 +33,3 @@ LABEL "org.cosmos.ibc-go" "${IBC_GO_VERSION}" COPY --from=builder /go/build/simd /bin/simd ENTRYPOINT ["simd"] - diff --git a/Makefile b/Makefile index 4e883c8d8bb..9b2eb21e34e 100644 --- a/Makefile +++ b/Makefile @@ -86,7 +86,6 @@ ifeq (,$(findstring nostrip,$(COSMOS_BUILD_OPTIONS))) endif ldflags += $(LDFLAGS) ldflags := $(strip $(ldflags)) - BUILD_FLAGS := -tags "$(build_tags)" -ldflags '$(ldflags)' # check for nostrip option ifeq (,$(findstring nostrip,$(COSMOS_BUILD_OPTIONS))) diff --git a/docs/architecture/adr-027-ibc-wasm.md b/docs/architecture/adr-027-ibc-wasm.md index 3e1f155f3b6..26ba89a1d77 100644 --- a/docs/architecture/adr-027-ibc-wasm.md +++ b/docs/architecture/adr-027-ibc-wasm.md @@ -3,6 +3,7 @@ ## Changelog - 26/11/2020: Initial Draft +- 26/05/2023: Update after 02-client refactor and re-implementation by Strangelove ## Status @@ -10,140 +11,169 @@ ## Abstract -In the Cosmos SDK light clients are current hardcoded in Go. This makes upgrading existing IBC light clients or adding -support for new light client a multi step process involving on-chain governance which is time-consuming. +In the Cosmos SDK light clients are current hardcoded in Go. This makes upgrading existing IBC light clients or +adding support for new light client a multi step process involving on-chain governance which is time-consuming. -To remedy this, we are proposing a WASM VM to host light client bytecode, which allows easier upgrading of -existing IBC light clients as well as adding support for new IBC light clients without requiring a code release and corresponding -hard-fork event. +To remedy this, we are proposing a Wasm VM to host light client bytecode, which allows easier upgrading of +existing IBC light clients as well as adding support for new IBC light clients without requiring a code release and +corresponding hard-fork event. ## Context -Currently in the SDK, light clients are defined as part of the codebase and are implemented as submodules under -`ibc-go/core/modules/light-clients/`. +Currently in ibc-go light clients are defined as part of the codebase and are implemented as modules under +`modules/light-clients`. Adding support for new light clients or updating an existing light client in the event +of a security issue or consensus update is a multi-step process which is both time consuming and error prone. +In order to enable new IBC light client implementations it is necessary to modify the codebase of ibc-go, +re-build chains' binaries, pass a governance proposal and validators upgrade their nodes. -Adding support for new light client or update an existing light client in the event of security -issue or consensus update is multi-step process which is both time consuming and error prone: +Another problem stemming from the above process is that if a chain wants to upgrade its own consensus, it will +need to convince every chain or hub connected to it to upgrade its light client in order to stay connected. Due +to the time consuming process required to upgrade a light client, a chain with lots of connections needs to be +disconnected for quite some time after upgrading its consensus, which can be very expensive in terms of time and effort. -1. To add support for new light client or update an existing light client in the - event of security issue or consensus update, we need to modify the codebase and integrate it in numerous places. +We are proposing simplifying this workflow by integrating a Wasm light client module that makes adding support for +new light clients a simple governance-gated transaction. The light client bytecode, written in Wasm-compilable Rust, +runs inside a Wasm VM. The Wasm light client submodule exposes a proxy light client interface that routes incoming +messages to the appropriate handler function, inside the Wasm VM for execution. -2. Governance voting: Adding new light client implementations require governance support and is expensive: This is - not ideal as chain governance is gatekeeper for new light client implementations getting added. If a small community - want support for light client X, they may not be able to convince governance to support it. - -3. Validator upgrade: After governance voting succeeds, validators need to upgrade their nodes in order to enable new - IBC light client implementation. - -Another problem stemming from the above process is that if a chain wants to upgrade its own consensus, it will need to convince every chain -or hub connected to it to upgrade its light client in order to stay connected. Due to time consuming process required -to upgrade light client, a chain with lots of connections needs to be disconnected for quite some time after upgrading -its consensus, which can be very expensive in terms of time and effort. - -We are proposing simplifying this workflow by integrating a WASM light client module which makes adding support for -a new light client a simple transaction. The light client bytecode, written in Wasm-compilable Rust, runs inside a WASM -VM. The Wasm light client submodule exposes a proxy light client interface that routes incoming messages to the -appropriate handler function, inside the Wasm VM for execution. - -With WASM light client module, anybody can add new IBC light client in the form of WASM bytecode (provided they are able to pay the requisite gas fee for the transaction) -as well as instantiate clients using any created client type. This allows any chain to update its own light client in other chains -without going through steps outlined above. +With the Wasm light client module, anybody can add new IBC light client in the form of Wasm bytecode (provided they are +able to submit the governance proposal transaction and that it passes) as well as instantiate clients using any created +client type. This allows any chain to update its own light client in other chains without going through the steps outlined above. ## Decision -We decided to use WASM light client module as a light client proxy which will interface with the actual light client -uploaded as WASM bytecode. This will require changing client selection method to allow any client if the client type -has prefix of `wasm/`. +We decided to implement the Wasm light client module as a light client proxy that will interface with the actual light client +uploaded as Wasm bytecode. To enable usage of the Wasm light client module, users need to add it to the list of allowed clients +by updating the `AllowedClients` parameter in the 02-client submodule of core IBC. ```go -// IsAllowedClient checks if the given client type is registered on the allowlist. -func (p Params) IsAllowedClient(clientType string) bool { - if p.AreWASMClientsAllowed && isWASMClient(clientType) { - return true - } +params := clientKeeper.GetParams(ctx) +params.AllowedClients = append(params.AllowedClients, exported.Wasm) +clientKeeper.SetParams(ctx, params) +``` - for _, allowedClient := range p.AllowedClients { - if allowedClient == clientType { - return true - } - } +Adding a new light client contract is governance-gated. To upload a new light client users need to submit +a [governance v1 proposal](https://docs.cosmos.network/main/modules/gov#proposals) that contains the `sdk.Msg` for storing +the Wasm contract's bytecode. The required message is `MsgStoreCode` and the bytecode is provided in the field `code`: - return false +```proto +// MsgStoreCode defines the request type for the StoreCode rpc. +message MsgStoreCode { + string signer = 1; + bytes code = 2; } ``` -To upload new light client, user need to create a transaction with Wasm byte code which will be -processed by IBC Wasm module. +The RPC handler processing `MsgStoreCode` will make sure that the signer of the message matches the address of authority allowed to +submit this message (which is normally the address of the governance module). ```go -func (k Keeper) UploadLightClient (wasmCode: []byte, description: String) { - wasmRegistry = getWASMRegistry() - id := hex.EncodeToString(sha256.Sum256(wasmCode)) - assert(!wasmRegistry.Exists(id)) - assert(wasmRegistry.ValidateAndStoreCode(id, description, wasmCode, false)) +// StoreCode defines a rpc handler method for MsgStoreCode +func (k Keeper) StoreCode(goCtx context.Context, msg *types.MsgStoreCode) (*types.MsgStoreCodeResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + if k.authority != msg.Signer { + return nil, sdkerrors.Wrapf(govtypes.ErrInvalidSigner, "invalid authority: expected %s, got %s", k.authority, msg.Signer) + } + + codeHash, err := k.storeWasmCode(ctx, msg.Code) + if err != nil { + return nil, sdkerrors.Wrap(err, "storing wasm code failed") + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + clienttypes.EventTypeStoreWasmCode, + sdk.NewAttribute(clienttypes.AttributeKeyWasmCodeHash, hex.EncodeToString(codeHash)), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, clienttypes.AttributeValueCategory), + ), + }) + + return &types.MsgStoreCodeResponse{ + CodeHash: codeHash, + }, nil } ``` -As name implies, Wasm registry is a registry which stores set of Wasm client code indexed by its hash and allows -client code to retrieve latest code uploaded. - -`ValidateAndStoreCode` checks if the wasm bytecode uploaded is valid and confirms to VM interface. +The contract's bytecode is stored in state in an entry indexed by the code hash: `codeHash/{code hash}`. The code hash is simply +the hash of the bytecode of the contract. ### How light client proxy works? -The light client proxy behind the scenes will call a cosmwasm smart contract instance with incoming arguments in json -serialized format with appropriate environment information. Data returned by the smart contract is deserialized and +The light client proxy behind the scenes will call a CosmWasm smart contract instance with incoming arguments serialized +in JSON format with appropriate environment information. Data returned by the smart contract is deserialized and returned to the caller. -Consider an example of `CheckProposedHeaderAndUpdateState` function of `ClientState` interface. Incoming arguments are -packaged inside a payload which is json serialized and passed to `callContract` which calls `vm.Execute` and returns the -array of bytes returned by the smart contract. This data is deserialized and passed as return argument. +Consider the example of the `VerifyClientMessage` function of `ClientState` interface. Incoming arguments are +packaged inside a payload object that is then JSON serialized and passed to `callContract`, which execute `WasmVm.Execute` +and returns the slice of bytes returned by the smart contract. This data is deserialized and passed as return argument. ```go -func (c *ClientState) CheckProposedHeaderAndUpdateState(context sdk.Context, marshaler codec.BinaryMarshaler, store sdk.KVStore, header exported.ClientMessage) (exported.ClientState, exported.ConsensusState, error) { - // get consensus state corresponding to client state to check if the client is expired - consensusState, err := GetConsensusState(store, marshaler, c.LatestHeight) - if err != nil { - return nil, nil, sdkerrors.Wrapf( - err, "could not get consensus state from clientstore at height: %d", c.LatestHeight, - ) +type ( + verifyClientMessageInnerPayload struct { + ClientMessage clientMessage `json:"client_message"` } - - payload := make(map[string]map[string]interface{}) - payload[CheckProposedHeaderAndUpdateState] = make(map[string]interface{}) - inner := payload[CheckProposedHeaderAndUpdateState] - inner["me"] = c - inner["header"] = header - inner["consensus_state"] = consensusState - - encodedData, err := json.Marshal(payload) - if err != nil { - return nil, nil, sdkerrors.Wrapf(ErrUnableToMarshalPayload, fmt.Sprintf("underlying error: %s", err.Error())) + clientMessage struct { + Header *Header `json:"header,omitempty"` + Misbehaviour *Misbehaviour `json:"misbehaviour,omitempty"` } - out, err := callContract(c.CodeId, context, store, encodedData) - if err != nil { - return nil, nil, sdkerrors.Wrapf(ErrUnableToCall, fmt.Sprintf("underlying error: %s", err.Error())) + verifyClientMessagePayload struct { + VerifyClientMessage verifyClientMessageInnerPayload `json:"verify_client_message"` + } +) + +// VerifyClientMessage must verify a ClientMessage. A ClientMessage could be a Header, Misbehaviour, or batch update. +// It must handle each type of ClientMessage appropriately. Calls to CheckForMisbehaviour, UpdateState, and UpdateStateOnMisbehaviour +// will assume that the content of the ClientMessage has been verified and can be trusted. An error should be returned +// if the ClientMessage fails to verify. +func (cs ClientState) VerifyClientMessage( + ctx sdk.Context, + _ codec.BinaryCodec, + clientStore sdk.KVStore, + clientMsg exported.ClientMessage +) error { + clientMsgConcrete := clientMessage{ + Header: nil, + Misbehaviour: nil, + } + switch clientMsg := clientMsg.(type) { + case *Header: + clientMsgConcrete.Header = clientMsg + case *Misbehaviour: + clientMsgConcrete.Misbehaviour = clientMsg } - output := clientStateCallResponse{} - if err := json.Unmarshal(out.Data, &output); err != nil { - return nil, nil, sdkerrors.Wrapf(ErrUnableToUnmarshalPayload, fmt.Sprintf("underlying error: %s", err.Error())) + inner := verifyClientMessageInnerPayload{ + ClientMessage: clientMsgConcrete, } - if !output.Result.IsValid { - return nil, nil, fmt.Errorf("%s error ocurred while updating client state", output.Result.ErrorMsg) + payload := verifyClientMessagePayload{ + VerifyClientMessage: inner, } - output.resetImmutables(c) - return output.NewClientState, output.NewConsensusState, nil + _, err := call[contractResult](ctx, clientStore, &cs, payload) + return err } ``` +### Global Wasm VM variable + +The 08-wasm keeper structure keeps a reference to the Wasm VM instantiated in the keeper constructor function. The keeper uses +the Wasm VM to store the bytecode of light client contracts. However, the Wasm VM is also needed in the 08-wasm implementations of +some of the `ClientState` interface functions to initialise a contract, execute calls on the contract and query the contract. Since +the `ClientState` functions do not have access to the 08-wasm keeper, then it has been decided to keep a global pointer variable that +points to the same instance as the one in the 08-wasm keeper. This global pointer variable is then used in the implementations of +the `ClientState` functions. + ## Consequences ### Positive -- Adding support for new light client or upgrading existing light client is way easier than before and only requires single transaction. -- Improves maintainability of Cosmos SDK, since no change in codebase is required to support new client or upgrade it. +- Adding support for new light client or upgrading existing light client is way easier than before and only requires single transaction instead of a hard-fork. +- Improves maintainability of ibc-go, since no change in codebase is required to support new client or upgrade it. +- The existence of support for Rust dependencies in light clients which may not exist in Go. ### Negative -- Light clients need to be written in subset of rust which could compile in Wasm. +- Light clients written in Rust need to be written in a subset of Rust which could compile in Wasm. - Introspecting light client code is difficult as only compiled bytecode exists in the blockchain. diff --git a/docs/client/config.json b/docs/client/config.json index 02c537956e1..5c9b23da836 100644 --- a/docs/client/config.json +++ b/docs/client/config.json @@ -61,6 +61,14 @@ "Params": "ChannelParams" } } + }, + { + "url": "./tmp-swagger-gen/ibc/lightclients/wasm/v1/query.swagger.json", + "operationIds": { + "rename": { + "Params": "WasmParams" + } + } } ] } diff --git a/docs/client/swagger-ui/swagger.yaml b/docs/client/swagger-ui/swagger.yaml index 1a6b9d8f140..c26828c49c9 100644 --- a/docs/client/swagger-ui/swagger.yaml +++ b/docs/client/swagger-ui/swagger.yaml @@ -698,6 +698,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -737,7 +741,6 @@ paths: name "y.z". - JSON @@ -1041,6 +1044,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -1080,7 +1087,6 @@ paths: name "y.z". - JSON @@ -1325,6 +1331,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -1364,7 +1374,6 @@ paths: name "y.z". - JSON @@ -1545,6 +1554,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -1584,7 +1597,6 @@ paths: name "y.z". - JSON @@ -1866,6 +1878,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -1905,7 +1921,6 @@ paths: name "y.z". - JSON @@ -2115,6 +2130,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -2154,7 +2173,6 @@ paths: name "y.z". - JSON @@ -2358,6 +2376,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -2397,7 +2419,6 @@ paths: name "y.z". - JSON @@ -2601,6 +2622,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -2640,7 +2665,6 @@ paths: name "y.z". - JSON @@ -2858,6 +2882,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -2897,7 +2925,6 @@ paths: name "y.z". - JSON @@ -3255,6 +3282,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -3294,7 +3325,6 @@ paths: name "y.z". - JSON @@ -3516,6 +3546,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -3555,7 +3589,6 @@ paths: name "y.z". - JSON @@ -3749,6 +3782,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -3788,7 +3825,6 @@ paths: name "y.z". - JSON @@ -3994,6 +4030,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -4033,7 +4073,6 @@ paths: name "y.z". - JSON @@ -4224,6 +4263,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -4263,7 +4306,6 @@ paths: name "y.z". - JSON @@ -4440,6 +4482,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -4479,7 +4525,6 @@ paths: name "y.z". - JSON @@ -4671,6 +4716,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -4710,7 +4759,6 @@ paths: name "y.z". - JSON @@ -4903,6 +4951,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -4942,7 +4994,6 @@ paths: name "y.z". - JSON @@ -5240,6 +5291,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -5279,7 +5334,6 @@ paths: name "y.z". - JSON @@ -5494,6 +5548,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -5533,7 +5591,6 @@ paths: name "y.z". - JSON @@ -5729,6 +5786,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -5768,7 +5829,6 @@ paths: name "y.z". - JSON @@ -5981,6 +6041,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -6020,7 +6084,6 @@ paths: name "y.z". - JSON @@ -6169,6 +6232,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -6208,7 +6275,6 @@ paths: name "y.z". - JSON @@ -6361,6 +6427,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -6400,7 +6470,6 @@ paths: name "y.z". - JSON @@ -6549,6 +6618,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -6588,7 +6661,6 @@ paths: name "y.z". - JSON @@ -6741,6 +6813,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -6780,7 +6856,6 @@ paths: name "y.z". - JSON @@ -6987,6 +7062,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -7026,7 +7105,6 @@ paths: name "y.z". - JSON @@ -7346,6 +7424,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -7385,7 +7467,6 @@ paths: name "y.z". - JSON @@ -7740,6 +7821,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -7779,7 +7864,6 @@ paths: name "y.z". - JSON @@ -7944,6 +8028,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -7983,7 +8071,6 @@ paths: name "y.z". - JSON @@ -8177,6 +8264,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -8216,7 +8307,6 @@ paths: name "y.z". - JSON @@ -8373,6 +8463,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -8412,7 +8506,6 @@ paths: name "y.z". - JSON @@ -8601,6 +8694,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -8640,7 +8737,6 @@ paths: name "y.z". - JSON @@ -8839,6 +8935,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -8878,7 +8978,6 @@ paths: name "y.z". - JSON @@ -9183,6 +9282,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -9222,7 +9325,6 @@ paths: name "y.z". - JSON @@ -9558,6 +9660,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -9597,7 +9703,6 @@ paths: name "y.z". - JSON @@ -9769,6 +9874,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -9808,7 +9917,6 @@ paths: name "y.z". - JSON @@ -10002,6 +10110,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -10041,7 +10153,6 @@ paths: name "y.z". - JSON @@ -10203,6 +10314,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -10242,7 +10357,6 @@ paths: name "y.z". - JSON @@ -10431,6 +10545,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -10470,7 +10588,6 @@ paths: name "y.z". - JSON @@ -10699,6 +10816,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -10738,7 +10859,6 @@ paths: name "y.z". - JSON @@ -10953,6 +11073,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -10992,7 +11116,6 @@ paths: name "y.z". - JSON @@ -11261,6 +11384,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -11300,7 +11427,6 @@ paths: name "y.z". - JSON @@ -11584,6 +11710,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -11623,7 +11753,6 @@ paths: name "y.z". - JSON @@ -11896,6 +12025,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -11935,7 +12068,6 @@ paths: name "y.z". - JSON @@ -12208,6 +12340,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -12247,7 +12383,6 @@ paths: name "y.z". - JSON @@ -12474,6 +12609,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -12513,7 +12652,6 @@ paths: name "y.z". - JSON @@ -12743,6 +12881,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -12782,7 +12924,6 @@ paths: name "y.z". - JSON @@ -13011,6 +13152,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -13050,7 +13195,6 @@ paths: name "y.z". - JSON @@ -13374,6 +13518,10 @@ paths: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -13413,7 +13561,6 @@ paths: name "y.z". - JSON @@ -13516,6 +13663,168 @@ paths: type: boolean tags: - Query + /ibc/lightclients/wasm/v1/code_hashes: + get: + summary: Get all Wasm code hashes + operationId: CodeHashes + responses: + '200': + description: A successful response. + schema: + type: object + properties: + code_hashes: + type: array + items: + type: string + pagination: + description: pagination defines an optional pagination for the request. + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: >- + QueryCodeHashesResponse is the response type for the + Query/CodeHashes RPC method. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + value: + type: string + format: byte + parameters: + - name: pagination.key + description: |- + key is a value returned in PageResponse.next_key to begin + querying the next page most efficiently. Only one of offset or key + should be set. + in: query + required: false + type: string + format: byte + - name: pagination.offset + description: >- + offset is a numeric offset that can be used when key is unavailable. + + It is less efficient than using key. Only one of offset or key + should + + be set. + in: query + required: false + type: string + format: uint64 + - name: pagination.limit + description: >- + limit is the total number of results to be returned in the result + page. + + If left empty it will default to a value to be set by each app. + in: query + required: false + type: string + format: uint64 + - name: pagination.count_total + description: >- + count_total is set to true to indicate that the result set should + include + + a count of the total number of items available for pagination in + UIs. + + count_total is only respected when offset is used. It is ignored + when key + + is set. + in: query + required: false + type: boolean + - name: pagination.reverse + description: >- + reverse is set to true if results are to be returned in the + descending order. + + + Since: cosmos-sdk 0.43 + in: query + required: false + type: boolean + tags: + - Query + /ibc/lightclients/wasm/v1/code_hashes/{code_hash}/code: + get: + summary: Get Wasm code for given code hash + operationId: Code + responses: + '200': + description: A successful response. + schema: + type: object + properties: + data: + type: string + format: byte + description: >- + QueryCodeResponse is the response type for the Query/Code RPC + method. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + value: + type: string + format: byte + parameters: + - name: code_hash + in: path + required: true + type: string + tags: + - Query definitions: cosmos.base.query.v1beta1.PageRequest: type: object @@ -13694,6 +14003,10 @@ definitions: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -13729,7 +14042,6 @@ definitions: name "y.z". - JSON @@ -13870,6 +14182,10 @@ definitions: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -13905,7 +14221,6 @@ definitions: name "y.z". - JSON @@ -15045,6 +15360,10 @@ definitions: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -15080,7 +15399,6 @@ definitions: name "y.z". - JSON @@ -15244,6 +15562,10 @@ definitions: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -15279,7 +15601,6 @@ definitions: name "y.z". - JSON @@ -15452,6 +15773,10 @@ definitions: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -15487,7 +15812,6 @@ definitions: name "y.z". - JSON @@ -15671,6 +15995,10 @@ definitions: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -15709,7 +16037,6 @@ definitions: name "y.z". - JSON @@ -15958,6 +16285,10 @@ definitions: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -15993,7 +16324,6 @@ definitions: name "y.z". - JSON @@ -16208,6 +16538,10 @@ definitions: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -16246,7 +16580,6 @@ definitions: name "y.z". - JSON @@ -16413,6 +16746,10 @@ definitions: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -16448,7 +16785,6 @@ definitions: name "y.z". - JSON @@ -16581,6 +16917,10 @@ definitions: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -16616,7 +16956,6 @@ definitions: name "y.z". - JSON @@ -17027,6 +17366,10 @@ definitions: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -17063,7 +17406,6 @@ definitions: name "y.z". - JSON @@ -17232,6 +17574,10 @@ definitions: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -17267,7 +17613,6 @@ definitions: name "y.z". - JSON @@ -17955,6 +18300,10 @@ definitions: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -17991,7 +18340,6 @@ definitions: name "y.z". - JSON @@ -18160,6 +18508,10 @@ definitions: if (any.is(Foo.class)) { foo = any.unpack(Foo.class); } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } Example 3: Pack and unpack a message in Python. @@ -18195,7 +18547,6 @@ definitions: name "y.z". - JSON @@ -19146,4 +19497,40 @@ definitions: - STATE_OPEN: A channel has completed the handshake. Open channels are ready to send and receive packets. - STATE_CLOSED: A channel has been closed and can no longer be used to send or receive - packets. \ No newline at end of file + packets. + ibc.lightclients.wasm.v1.QueryCodeHashesResponse: + type: object + properties: + code_hashes: + type: array + items: + type: string + pagination: + description: pagination defines an optional pagination for the request. + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: >- + QueryCodeHashesResponse is the response type for the Query/CodeHashes RPC + method. + ibc.lightclients.wasm.v1.QueryCodeResponse: + type: object + properties: + data: + type: string + format: byte + description: QueryCodeResponse is the response type for the Query/Code RPC method. diff --git a/docs/docs/01-ibc/06-proposals.md b/docs/docs/01-ibc/06-proposals.md index 2d8d8ba1e06..13b27fbd364 100644 --- a/docs/docs/01-ibc/06-proposals.md +++ b/docs/docs/01-ibc/06-proposals.md @@ -79,7 +79,7 @@ The client is attached to the expected Akash `chain_id`. Note that although the ### Step 2 Anyone can submit the governance proposal to recover the client by executing the following via CLI. -If the chain is on an ibc-go version older than v8, please see the [relevant documentation](https://ibc.cosmos.network/v7.3.x/ibc/proposals.html). +If the chain is on an ibc-go version older than v8, please see the [relevant documentation](https://ibc.cosmos.network/v7/ibc/proposals). - From ibc-go v8 onwards diff --git a/docs/docs/03-light-clients/01-developer-guide/wasm/audits/Ethan Frey - Wasm Client Review.pdf b/docs/docs/03-light-clients/01-developer-guide/wasm/audits/Ethan Frey - Wasm Client Review.pdf new file mode 100644 index 00000000000..12300a49b3f Binary files /dev/null and b/docs/docs/03-light-clients/01-developer-guide/wasm/audits/Ethan Frey - Wasm Client Review.pdf differ diff --git a/docs/docs/03-light-clients/01-developer-guide/wasm/audits/Halborn audit report.pdf b/docs/docs/03-light-clients/01-developer-guide/wasm/audits/Halborn audit report.pdf new file mode 100644 index 00000000000..df97d6f4a07 Binary files /dev/null and b/docs/docs/03-light-clients/01-developer-guide/wasm/audits/Halborn audit report.pdf differ diff --git a/docs/docs/03-light-clients/04-wasm/01-overview.md b/docs/docs/03-light-clients/04-wasm/01-overview.md new file mode 100644 index 00000000000..3214ca775d3 --- /dev/null +++ b/docs/docs/03-light-clients/04-wasm/01-overview.md @@ -0,0 +1,26 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /ibc/light-clients/wasm/overview +--- + +# `08-wasm` + +## Overview + +Learn about the `08-wasm` light client proxy module. {synopsis} + +### Context + +Traditionally, light clients used by ibc-go have been implemented only in Go, and since ibc-go v7 (with the release of the 02-client refactor), they are [first-class Cosmos SDK modules](../../../architecture/adr-010-light-clients-as-sdk-modules.md). This means that updating existing light client implementations or adding support for new light clients is a multi-step, time-consuming process involving on-chain governance: it is necessary to modify the codebase of ibc-go (if the light client is part of its codebase), re-build chains' binaries, pass a governance proposal and have validators upgrade their nodes. + +### Motivation + +To break the limitation of being able to write light client implementations only in Go, the `08-wasm` adds support to run light clients written in a Wasm-compilable language. The light client byte code implements the entry points of a [CosmWasm](https://docs.cosmwasm.com/docs/) smart contract, and runs inside a Wasm VM. The `08-wasm` module exposes a proxy light client interface that routes incoming messages to the appropriate handler function, inside the Wasm VM, for execution. + +Adding a new light client to a chain is just as simple as submitting a governance proposal with the message that stores the byte code of the light client contract. No coordinated upgrade is needed. When the governance proposal passes and the message is executed, the contract is ready to be instantiated upon receiving a relayer-submitted `MsgCreateClient`. The process of creating a Wasm light client is the same as with a regular light client implemented in Go. + +### Use cases + +- Development of light clients for non-Cosmos ecosystem chains: state machines in other ecosystems are, in many cases, implemented in Rust, and thus there are probably libraries used in their light client implementations for which there is no equivalent in Go. This makes the development of a light client in Go very difficult, but relatively simple to do it in Rust. Therefore, writing a CosmWasm smart contract in Rust that implements the light client algorithm becomes a lower effort. diff --git a/docs/docs/03-light-clients/04-wasm/02-concepts.md b/docs/docs/03-light-clients/04-wasm/02-concepts.md new file mode 100644 index 00000000000..49c1c548975 --- /dev/null +++ b/docs/docs/03-light-clients/04-wasm/02-concepts.md @@ -0,0 +1,74 @@ +--- +title: Concepts +sidebar_label: Concepts +sidebar_position: 2 +slug: /ibc/light-clients/wasm/concepts +--- + +# Concepts + +Learn about the differences between a proxy light client and a Wasm light client. {synopsis} + +## Proxy light client + +The `08-wasm` module is not a regular light client in the same sense as, for example, the 07-tendermint light client. `08-wasm` is instead a *proxy* light client module, and this means that the module acts a proxy to the actual implementations of light clients. The module will act as a wrapper for the actual light clients uploaded as Wasm byte code and will delegate all operations to them (i.e. `08-wasm` just passes through the requests to the Wasm light clients). Still, the `08-wasm` module implements all the required interfaces necessary to integrate with core IBC, so that 02-client can call into it as it would for any other light client module. These interfaces are `ClientState`, `ConsensusState` and `ClientMessage`, and we will describe them in the context of `08-wasm` in the following sections. For more information about this set of interfaces, please read section [Overview of the light client module developer guide](../01-developer-guide/01-overview.md#overview). + +### `ClientState` + +The `08-wasm`'s `ClientState` data structure contains three fields: + +- `Data` contains the bytes of the Protobuf-encoded client state of the underlying light client implemented as a Wasm contract. For example, if the Wasm light client contract implements the GRANDPA light client algorithm, then `Data` will contain the bytes for a [GRANDPA client state](https://github.com/ComposableFi/composable-ibc/blob/02ce69e2843e7986febdcf795f69a757ce569272/light-clients/ics10-grandpa/src/proto/grandpa.proto#L35-L60). +- `CodeHash` is the sha256 hash of the Wasm contract's byte code. This hash is used as an identifier to call the right contract. +- `LatestHeight` is the latest height of the counterparty state machine (i.e. the height of the blockchain), whose consensus state the light client tracks. + +```go +type ClientState struct { + // bytes encoding the client state of the underlying + // light client implemented as a Wasm contract + Data []byte + // sha256 hash of Wasm contract byte code + CodeHash []byte + // latest height of the counterparty ledger + LatestHeight types.Height +} +``` + +See section [`ClientState` of the light client module developer guide](../01-developer-guide/01-overview.md#clientstate) for more information about the `ClientState` interface. + +### `ConsensusState` + +The `08-wasm`'s `ConsensusState` data structure maintains one field: + +- `Data` contains the bytes of the Protobuf-encoded consensus state of the underlying light client implemented as a Wasm contract. For example, if the Wasm light client contract implements the GRANDPA light client algorithm, then `Data` will contain the bytes for a [GRANDPA consensus state](https://github.com/ComposableFi/composable-ibc/blob/02ce69e2843e7986febdcf795f69a757ce569272/light-clients/ics10-grandpa/src/proto/grandpa.proto#L87-L94). + +```go +type ConsensusState struct { + // bytes encoding the consensus state of the underlying light client + // implemented as a Wasm contract. + Data []byte +} +``` + +See section [`ConsensusState` of the light client module developer guide](../01-developer-guide/01-overview.md#consensusstate) for more information about the `ConsensusState` interface. + +### `ClientMessage` + +`ClientMessage` is used for performing updates to a `ClientState` stored on chain. The `08-wasm`'s `ClientMessage` data structure maintains one field: + +- `Data` contains the bytes of the Protobuf-encoded header(s) or misbehaviour for the underlying light client implemented as a Wasm contract. For example, if the Wasm light client contract implements the GRANDPA light client algorithm, then `Data` will contain the bytes of either [header](https://github.com/ComposableFi/composable-ibc/blob/02ce69e2843e7986febdcf795f69a757ce569272/light-clients/ics10-grandpa/src/proto/grandpa.proto#L96-L104) or [misbehaviour](https://github.com/ComposableFi/composable-ibc/blob/02ce69e2843e7986febdcf795f69a757ce569272/light-clients/ics10-grandpa/src/proto/grandpa.proto#L106-L112) for a GRANDPA light client. + +```go +type ClientMessage struct { + // bytes encoding the header(s) or misbehaviour for the underlying light client + // implemented as a Wasm contract. + Data []byte +} +``` + +See section [`ClientMessage` of the light client module developer guide](../01-developer-guide/01-overview.md#clientmessage) for more information about the `ClientMessage` interface. + +## Wasm light client + +The actual light client can be implemented in any language that compiles to Wasm and implements the interfaces of a [CosmWasm](https://docs.cosmwasm.com/docs/) contract. Even though in theory other languages could be used, in practice (at least for the time being) the most suitable language to use would be Rust, since there is already good support for it for developing CosmWasm smart contracts. + +At the moment of writing there are two contracts available: one for [Tendermint](https://github.com/ComposableFi/composable-ibc/tree/master/light-clients/ics07-tendermint-cw) and one [GRANDPA](https://github.com/ComposableFi/composable-ibc/tree/master/light-clients/ics10-grandpa-cw) (which is being used in production in [Composable Finance's Centauri bridge](https://github.com/ComposableFi/composable-ibc)). And there are others in development (e.g. for Near). diff --git a/docs/docs/03-light-clients/04-wasm/03-integration.md b/docs/docs/03-light-clients/04-wasm/03-integration.md new file mode 100644 index 00000000000..82e70e1ebc6 --- /dev/null +++ b/docs/docs/03-light-clients/04-wasm/03-integration.md @@ -0,0 +1,248 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 3 +slug: /ibc/light-clients/wasm/integration +--- + +# Integration + +Learn how to integrate the `08-wasm` module in a chain binary and about the recommended approaches depending on whether the [`x/wasm` module](https://github.com/CosmWasm/wasmd/tree/main/x/wasm) is already used in the chain. The following document only applies for Cosmos SDK chains. {synopsis} + +## `app.go` setup + +The sample code below shows the relevant integration points in `app.go` required to setup the `08-wasm` module in a chain binary. Since `08-wasm` is a light client module itself, please check out as well the section [Integrating light clients](../../01-ibc/02-integration.md#integrating-light-clients) for more information: + +```go +// app.go +import ( + ... + "github.com/cosmos/cosmos-sdk/runtime" + + wasm "github.com/cosmos/ibc-go/modules/light-clients/08-wasm" + wasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" + wasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + ... +) + +... + +// Register the AppModule for the 08-wasm module +ModuleBasics = module.NewBasicManager( + ... + wasm.AppModuleBasic{}, + ... +) + +// Add 08-wasm Keeper +type SimApp struct { + ... + WasmClientKeeper wasmkeeper.Keeper + ... +} + +func NewSimApp( + logger log.Logger, + db dbm.DB, + traceStore io.Writer, + loadLatest bool, + appOpts servertypes.AppOptions, + baseAppOptions ...func(*baseapp.BaseApp), +) *SimApp { + ... + keys := sdk.NewKVStoreKeys( + ... + wasmtypes.StoreKey, + ) + // Instantiate 08-wasm's keeper + // This sample code uses a constructor function that + // accepts a pointer to an existing instance of Wasm VM. + // This is the recommended approach when the chain + // also uses `x/wasm`, and then the Wasm VM instance + // can be shared. + // See the section below for more information. + app.WasmClientKeeper = wasmkeeper.NewKeeperWithVM( + appCodec, + runtime.NewKVStoreService(keys[wasmtypes.StoreKey]), + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmVM, + ) + app.ModuleManager = module.NewManager( + // SDK app modules + ... + wasm.NewAppModule(app.WasmClientKeeper), + ) + app.ModuleManager.SetOrderBeginBlockers( + ... + wasmtypes.ModuleName, + ... + ) + app.ModuleManager.SetOrderEndBlockers( + ... + wasmtypes.ModuleName, + ... + ) + genesisModuleOrder := []string{ + ... + wasmtypes.ModuleName, + ... + } + app.ModuleManager.SetOrderInitGenesis(genesisModuleOrder...) + app.ModuleManager.SetOrderExportGenesis(genesisModuleOrder...) + ... + + // initialize BaseApp + app.SetInitChainer(app.InitChainer) + ... + + // must be before Loading version + if manager := app.SnapshotManager(); manager != nil { + err := manager.RegisterExtensions( + wasmkeeper.NewWasmSnapshotter(app.CommitMultiStore(), &app.WasmClientKeeper), + ) + if err != nil { + panic(fmt.Errorf("failed to register snapshot extension: %s", err)) + } + } + ... +} +``` + +## Keeper instantiation + +When it comes to instantiating `08-wasm`'s keeper there are two recommended ways of doing it. Choosing one or the other will depend on whether the chain already integrates [`x/wasm`](https://github.com/CosmWasm/wasmd/tree/main/x/wasm) or not. + +### If `x/wasm` is present + +If the chain where the module is integrated uses `x/wasm` then we recommend that both `08-wasm` and `x/wasm` share the same Wasm VM instance. Having two separate Wasm VM instances is still possible, but care should be taken to make sure that both instances do not share the directory when the VM stores blobs and various caches, otherwise unexpected behaviour is likely to happen. + +In order to share the Wasm VM instance please follow the guideline below. Please note that this requires `x/wasm`v0.41 or above. + +- Instantiate the Wasm VM in `app.go` with the parameters of your choice. +- [Create an `Option` with this Wasm VM instance](https://github.com/CosmWasm/wasmd/blob/db93d7b6c7bb6f4a340d74b96a02cec885729b59/x/wasm/keeper/options.go#L21-L25). +- Add the option created in the previous step to a slice and [pass it to the `x/wasm NewKeeper` constructor function](https://github.com/CosmWasm/wasmd/blob/db93d7b6c7bb6f4a340d74b96a02cec885729b59/x/wasm/keeper/keeper_cgo.go#L36). +- Pass the pointer to the Wasm VM instance to `08-wasm` [NewKeeperWithVM constructor function](https://github.com/cosmos/ibc-go/blob/c95c22f45cb217d27aca2665af9ac60b0d2f3a0c/modules/light-clients/08-wasm/keeper/keeper.go#L33-L38). + +The code to set this up would look something like this: + +```go +// app.go +import ( + ... + "github.com/cosmos/cosmos-sdk/runtime" + + wasmvm "github.com/CosmWasm/wasmvm" + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + ... +) + +... + +// instantiate the Wasm VM with the chosen parameters +wasmer, err := wasmvm.NewVM( + dataDir, + availableCapabilities, + contractMemoryLimit, + contractDebugMode, + memoryCacheSize, +) +if err != nil { + panic(err) +} + +// create an Option slice (or append to an existing one) +// with the option to use a custom Wasm VM instance +wasmOpts = []wasmkeeper.Option{ + wasmkeeper.WithWasmEngine(wasmer), +} + +// the keeper will use the provided Wasm VM instance, +// instead of instantiating a new one +app.WasmKeeper = wasmkeeper.NewKeeper( + appCodec, + keys[wasmtypes.StoreKey], + app.AccountKeeper, + app.BankKeeper, + app.StakingKeeper, + distrkeeper.NewQuerier(app.DistrKeeper), + app.IBCFeeKeeper, // ISC4 Wrapper: fee IBC middleware + app.IBCKeeper.ChannelKeeper, + &app.IBCKeeper.PortKeeper, + scopedWasmKeeper, + app.TransferKeeper, + app.MsgServiceRouter(), + app.GRPCQueryRouter(), + wasmDir, + wasmConfig, + availableCapabilities, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmOpts..., +) + +app.WasmClientKeeper = wasmkeeper.NewKeeperWithVM( + appCodec, + runtime.NewKVStoreService(keys[wasmtypes.StoreKey]), + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmer, // pass the Wasm VM instance to `08-wasm` keeper constructor +) +... +``` + +### If `x/wasm` is not present + +If the chain does not use [`x/wasm`](https://github.com/CosmWasm/wasmd/tree/main/x/wasm), even though it is still possible to use the method above from the previous section +(e.g. instantiating a Wasm VM in app.go an pass it to 08-wasm's [`NewKeeperWithVM` constructor function](https://github.com/cosmos/ibc-go/blob/c95c22f45cb217d27aca2665af9ac60b0d2f3a0c/modules/light-clients/08-wasm/keeper/keeper.go#L33-L38), since there would be no need in this case to share the Wasm VM instance with another module, you can use the [`NewKeeperWithConfig`` constructor function](https://github.com/cosmos/ibc-go/blob/c95c22f45cb217d27aca2665af9ac60b0d2f3a0c/modules/light-clients/08-wasm/keeper/keeper.go#L52-L57) and provide the Wasm VM configuration parameters of your choice instead. A Wasm VM instance will be created in`NewKeeperWithConfig`. The parameters that can set are: + +- `DataDir` is the [directory for Wasm blobs and various caches](https://github.com/CosmWasm/wasmvm/blob/1638725b25d799f078d053391945399cb35664b1/lib.go#L25). In `wasmd` this is set to the [`wasm` folder under the home directory](https://github.com/CosmWasm/wasmd/blob/36416def20effe47fb77f29f5ba35a003970fdba/app/app.go#L578). +- `SupportedCapabilities` is a comma separated [list of capabilities supported by the chain](https://github.com/CosmWasm/wasmvm/blob/1638725b25d799f078d053391945399cb35664b1/lib.go#L26). [`wasmd` sets this to all the available capabilities](https://github.com/CosmWasm/wasmd/blob/36416def20effe47fb77f29f5ba35a003970fdba/app/app.go#L586), but 08-wasm only requires `iterator`. +- `MemoryCacheSize` sets [the size in MiB of an in-memory cache for e.g. module caching](https://github.com/CosmWasm/wasmvm/blob/1638725b25d799f078d053391945399cb35664b1/lib.go#L29C16-L29C104). It is not consensus-critical and should be defined on a per-node basis, often in the range 100 to 1000 MB. [`wasmd` reads this value of](https://github.com/CosmWasm/wasmd/blob/36416def20effe47fb77f29f5ba35a003970fdba/app/app.go#L579). Default value is 256. +- `ContractDebugMode` is a [flag to enable/disable printing debug logs from the contract to STDOUT](https://github.com/CosmWasm/wasmvm/blob/1638725b25d799f078d053391945399cb35664b1/lib.go#L28). This should be false in production environments. Default value is false. + +Another configuration parameter of the Wasm VM is the contract memory limit (in MiB), which is [set to 32](https://github.com/cosmos/ibc-go/blob/c95c22f45cb217d27aca2665af9ac60b0d2f3a0c/modules/light-clients/08-wasm/types/config.go#L5), [following the example of `wasmd`](https://github.com/CosmWasm/wasmd/blob/36416def20effe47fb77f29f5ba35a003970fdba/x/wasm/keeper/keeper.go#L32-L34). This parameter is not configurable by users of `08-wasm`. + +The following sample code shows how the keeper would be constructed using this method: + +```go +// app.go +import ( + ... + "github.com/cosmos/cosmos-sdk/runtime" + + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + ... +) +... +wasmConfig := wasmtypes.WasmConfig{ + DataDir: "ibc_08-wasm_client_data", + SupportedCapabilities: "iterator", + ContractDebugMode: false, +} +app.WasmClientKeeper = wasmkeeper.NewKeeperWithConfig( + appCodec, + runtime.NewKVStoreService(keys[wasmtypes.StoreKey]), + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmConfig, +) +``` + +Check out also the [`WasmConfig` type definition](https://github.com/cosmos/ibc-go/blob/c95c22f45cb217d27aca2665af9ac60b0d2f3a0c/modules/light-clients/08-wasm/types/config.go#L7-L20) for more information on each of the configurable parameters. Some parameters allow node-level configurations. There is additionally the function [`DefaultWasmConfig`](https://github.com/cosmos/ibc-go/blob/6d8cee53a72524b7cf396d65f6c19fed45803321/modules/light-clients/08-wasm/types/config.go#L30) available that returns a configuration with the default values. + +## Updating `AllowedClients` + +In order to use the `08-wasm` module chains must update the [`AllowedClients` parameter in the 02-client submodule](https://github.com/cosmos/ibc-go/blob/main/proto/ibc/core/client/v1/client.proto#L103) of core IBC. This can be configured directly in the application upgrade handler with the sample code below: + +```go +params := clientKeeper.GetParams(ctx) +params.AllowedClients = append(params.AllowedClients, exported.Wasm) +clientKeeper.SetParams(ctx, params) +``` + +Or alternatively the parameter can be updated via a governance proposal (see at the bottom of section [`Creating clients`](../01-developer-guide/09-setup.md#creating-clients) for an example of how to do this). + +## Adding snapshot support + +In order to use the `08-wasm` module chains are required to register the `WasmSnapshotter` extension in the snapshot manager. This snapshotter takes care of persisting the external state, in the form of contract code, of the Wasm VM instance to disk when the chain is snapshotted. diff --git a/docs/docs/03-light-clients/04-wasm/04-messages.md b/docs/docs/03-light-clients/04-wasm/04-messages.md new file mode 100644 index 00000000000..911b54cae70 --- /dev/null +++ b/docs/docs/03-light-clients/04-wasm/04-messages.md @@ -0,0 +1,75 @@ +--- +title: Messages +sidebar_label: Messages +sidebar_position: 4 +slug: /ibc/light-clients/wasm/messages +--- + +# Messages + +## `MsgStoreCode` + +Uploading the Wasm light client contract to the Wasm VM storage is achieved by means of `MsgStoreCode`: + +```go +type MsgStoreCode struct { + // signer address + Signer string + // wasm byte code of light client contract. It can be raw or gzip compressed + WasmByteCode []byte +} +``` + +This message is expected to fail if: + +- `Signer` is an invalid Bech32 address, or it does not match the designated authority address. +- `WasmByteCode` is empty or it exceeds the maximum size, currently set to 3MB. + +Only light client contracts stored using `MsgStoreCode` are allowed to be instantiated. An attempt to create a light client from contracts uploaded via other means (e.g. through `x/wasm` if the module shares the same Wasm VM instance with 08-wasm) will fail. Due to the idempotent nature of the Wasm VM's `StoreCode` function, it is possible to store the same byte code multiple times. + +When execution of `MsgStoreCode` succeeds, the code hash of the contract (i.e. the sha256 hash of the contract's byte code) is stored in an allow list. When a relayer submits [`MsgCreateClient`](https://github.com/cosmos/ibc-go/blob/v7.2.0/proto/ibc/core/client/v1/tx.proto#L25-L37) with 08-wasm's `ClientState`, the client state includes the code hash of the Wasm byte code that should be called. Then 02-client calls [08-wasm's implementation of `Initialize` function](https://github.com/cosmos/ibc-go/blob/v7.2.0/modules/core/02-client/keeper/client.go#L34) (which is an interface function part of `ClientState`), and it will check that the code hash in the client state matches one of the code hashes in the allow list. If a match is found, the light client is initialized; otherwise, the transaction is aborted. + +## `MsgMigrateContract` + +Migrating a contract to a new Wasm byte code is achieved by means of `MsgMigrateContract`: + +```go +type MsgMigrateContract struct { + // signer address + Signer string + // the client id of the contract + ClientId string + // the SHA-256 hash of the new wasm byte code for the contract + CodeHash []byte + // the json-encoded migrate msg to be passed to the contract on migration + Msg []byte +} +``` + +This message is expected to fail if: + +- `Signer` is an invalid Bech32 address, or it does not match the designated authority address. +- `ClientId` is not a valid identifier prefixed by `08-wasm`. +- `CodeHash` is not exactly 32 bytes long or it is not found in the list of allowed code hashes (a new code hash is added to the list when executing `MsgStoreCode`), or it matches the current code hash of the contract. + +When a Wasm light client contract is migrated to a new Wasm byte code the code hash for the contract will be updated with the new code hash. + +## `MsgRemoveCodeHash` + +Removing a code hash from the list of allowed code hashes is achieved by means of `MsgRemoveCodeHash`: + +```go +type MsgRemoveCodeHash struct { + // signer address + Signer string + // code hash to be removed from the store + CodeHash []byte +} +``` + +This message is expected to fail if: + +- `Signer` is an invalid Bech32 address, or it does not match the designated authority address. +- `CodeHash` is not exactly 32 bytes long or it is not found in the list of allowed code hashes (a new code hash is added to the list when executing `MsgStoreCode`). + +When a code hash is removed from the list of allowed code hashes, then the corresponding Wasm byte code will not be available for instantiation in [08-wasm's implementation of `Initialize` function](https://github.com/cosmos/ibc-go/blob/v7.2.0/modules/core/02-client/keeper/client.go#L34). diff --git a/docs/docs/03-light-clients/04-wasm/05-governance.md b/docs/docs/03-light-clients/04-wasm/05-governance.md new file mode 100644 index 00000000000..ecd0fc36bd0 --- /dev/null +++ b/docs/docs/03-light-clients/04-wasm/05-governance.md @@ -0,0 +1,125 @@ +--- +title: Governance +sidebar_label: Governance +sidebar_position: 5 +slug: /ibc/light-clients/wasm/governance +--- + +# Governance + +Learn how to upload Wasm light client byte code on a chain, and how to migrate an existing Wasm light client contract. {synopsis} + +## Setting an authority + +Both the storage of Wasm light client byte code as well as the migration of an existing Wasm light client contract are permissioned (i.e. only allowed to an authority such as governance). The designated authority is specified when instantiating `08-wasm`'s keeper: both [`NewKeeperWithVM`](https://github.com/cosmos/ibc-go/blob/c95c22f45cb217d27aca2665af9ac60b0d2f3a0c/modules/light-clients/08-wasm/keeper/keeper.go#L33-L38) and [`NewKeeperWithConfig`](https://github.com/cosmos/ibc-go/blob/c95c22f45cb217d27aca2665af9ac60b0d2f3a0c/modules/light-clients/08-wasm/keeper/keeper.go#L52-L57) constructor functions accept an `authority` argument that must be the address of the authorized actor. For example, in `app.go`, when instantiating the keeper, you can pass the address of the governance module: + +```go +// app.go +import ( + ... + "github.com/cosmos/cosmos-sdk/runtime" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + wasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" + wasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + ... +) + +// app.go +app.WasmClientKeeper = wasmkeeper.NewKeeperWithVM( + appCodec, + runtime.NewKVStoreService(keys[wasmtypes.StoreKey]), + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), // authority + wasmVM, +) +``` + +## Storing new Wasm light client byte code + + If governance is the allowed authority, the governance v1 proposal that needs to be submitted to upload a new light client contract should contain the message [`MsgStoreCode`](https://github.com/cosmos/ibc-go/blob/f822b4fa7932a657420aba219c563e06c4465221/proto/ibc/lightclients/wasm/v1/tx.proto#L16-L23) with the base64-encoded byte code of the Wasm contract. Use the following CLI command and JSON as an example: + +```shell +simd tx gov submit-proposal --from +``` + +where `proposal.json` contains: + +```json +{ + "title": "Upload IBC Wasm light client", + "summary": "Upload wasm client", + "messages": [ + { + "@type": "/ibc.lightclients.wasm.v1.MsgStoreCode", + "signer": "cosmos1...", // the authority address (e.g. the gov module account address) + "wasm_byte_code": "YWJ...PUB+" // standard base64 encoding of the Wasm contract byte code + } + ], + "metadata": "AQ==", + "deposit": "100stake" +} +``` + +To learn more about the `submit-proposal` CLI command, please check out [the relevant section in Cosmos SDK documentation](https://docs.cosmos.network/main/modules/gov#submit-proposal). + +Alternatively, the process of submitting the proposal may be simpler if you use the CLI command `store-code`. This CLI command accepts as argument the file of the Wasm light client contract and takes care of constructing the proposal message with `MsgStoreCode` and broadcasting it. See section [`store-code`](./08-client.md#store-code) for more information. + +## Migrating an existing Wasm light client contract + +If governance is the allowed authority, the governance v1 proposal that needs to be submitted to migrate an existing new Wasm light client contract should contain the message [`MsgMigrateContract`](https://github.com/cosmos/ibc-go/blob/729cb090951b1e996427b2258cf72c49787b885a/proto/ibc/lightclients/wasm/v1/tx.proto#L51-L63) with the code hash of the Wasm byte code to migrate to. Use the following CLI command and JSON as an example: + +```shell +simd tx gov submit-proposal --from +``` + +where `proposal.json` contains: + +```json +{ + "title": "Migrate IBC Wasm light client", + "summary": "Migrate wasm client", + "messages": [ + { + "@type": "/ibc.lightclients.wasm.v1.MsgMigrateContract", + "signer": "cosmos1...", // the authority address (e.g. the gov module account address) + "client_id": "08-wasm-1", // client identifier of the Wasm light client contract that will be migrated + "new_code_hash": "a8ad...4dc0", // SHA-256 hash of the Wasm byte code to migrate to, previously stored with MsgStoreCode + "msg": "{}" // JSON-encoded message to be passed to the contract on migration + } + ], + "metadata": "AQ==", + "deposit": "100stake" +} +``` + +To learn more about the `submit-proposal` CLI command, please check out [the relevant section in Cosmos SDK documentation](https://docs.cosmos.network/main/modules/gov#submit-proposal). + +## Removing an existing code hash + +If governance is the allowed authority, the governance v1 proposal that needs to be submitted to remove a specific code hash from the -list of allowed code hashes should contain the message [`MsgRemoveCodeHash`](https://github.com/cosmos/ibc-go/blob/729cb090951b1e996427b2258cf72c49787b885a/proto/ibc/lightclients/wasm/v1/tx.proto#L38-L46) with the code hash (of a corresponding Wasm byte code). Use the following CLI command and JSON as an example: + +```shell +simd tx gov submit-proposal --from +``` + +where `proposal.json` contains: + +```json +{ + "title": "Remove code hash of Wasm light client byte code", + "summary": "Remove code hash", + "messages": [ + { + "@type": "/ibc.lightclients.wasm.v1.MsgRemoveCodeHash", + "signer": "cosmos1...", // the authority address (e.g. the gov module account address) + "code_hash": "a8ad...4dc0", // SHA-256 hash of the Wasm byte code that should be removed from the list of allowed code hashes + } + ], + "metadata": "AQ==", + "deposit": "100stake" +} +``` + +To learn more about the `submit-proposal` CLI command, please check out [the relevant section in Cosmos SDK documentation](https://docs.cosmos.network/main/modules/gov#submit-proposal). diff --git a/docs/docs/03-light-clients/04-wasm/06-events.md b/docs/docs/03-light-clients/04-wasm/06-events.md new file mode 100644 index 00000000000..58277f3f2ee --- /dev/null +++ b/docs/docs/03-light-clients/04-wasm/06-events.md @@ -0,0 +1,26 @@ +--- +title: Events +sidebar_label: Events +sidebar_position: 6 +slug: /ibc/light-clients/wasm/events +--- + +# Events + +The `08-wasm` module emits the following events: + +## `MsgStoreCode` + +| Type | Attribute Key | Attribute Value | +|------------------|----------------|------------------------| +| store_wasm_code | wasm_code_hash | {hex.Encode(codeHash)} | +| message | module | 08-wasm | + +## `MsgMigrateContract` + +| Type | Attribute Key | Attribute Value | +|------------------|----------------|---------------------------| +| migrate_contract | client_id | {clientId} | +| migrate_contract | wasm_code_hash | {hex.Encode(codeHash)} | +| migrate_contract | new_code_hash | {hex.Encode(newCodeHash)} | +| message | module | 08-wasm | diff --git a/docs/docs/03-light-clients/04-wasm/07-contracts.md b/docs/docs/03-light-clients/04-wasm/07-contracts.md new file mode 100644 index 00000000000..00630daf317 --- /dev/null +++ b/docs/docs/03-light-clients/04-wasm/07-contracts.md @@ -0,0 +1,112 @@ +--- +title: Contracts +sidebar_label: Contracts +sidebar_position: 7 +slug: /ibc/light-clients/wasm/contracts +--- + +# Contracts + +Learn about the expected behaviour of Wasm light client contracts and the between with `08-wasm`. {synopsis} + +## API + +The `08-wasm` light client proxy performs calls to the Wasm light client via the Wasm VM. The calls require as input JSON-encoded payload messages that fall in the three categories described in the next sections. + +## `InstantiateMessage` + +This is the message sent to the contract's `instantiate` entry point. It contains the client and consensus state provided in [`MsgCreateClient`](https://github.com/cosmos/ibc-go/blob/v7.2.0/proto/ibc/core/client/v1/tx.proto#L25-L37). + +```go +type InstantiateMessage struct { + ClientState *ClientState `json:"client_state"` + ConsensusState *ConsensusState `json:"consensus_state"` +} +``` + +The Wasm light client contract is expected to store the client and consensus state in the corresponding keys of the client-prefixed store. + +## `QueryMsg` + +`QueryMsg` acts as a discriminated union type that is used to encode the messages that are sent to the contract's `query` entry point. Only one of the fields of the type should be set at a time, so that the other fields are omitted in the encoded JSON and the payload can be correctly translated to the corresponding element of the enumeration in Rust. + +```go +type QueryMsg struct { + Status *StatusMsg `json:"status,omitempty"` + ExportMetadata *ExportMetadataMsg `json:"export_metadata,omitempty"` + TimestampAtHeight *TimestampAtHeightMsg `json:"timestamp_at_height,omitempty"` + VerifyClientMessage *VerifyClientMessageMsg `json:"verify_client_message,omitempty"` + CheckForMisbehaviour *CheckForMisbehaviourMsg `json:"check_for_misbehaviour,omitempty"` +} +``` + +```rust +#[cw_serde] +pub enum QueryMsg { + Status(StatusMsg), + ExportMetadata(ExportMetadataMsg), + TimestampAtHeight(TimestampAtHeightMsg), + VerifyClientMessage(VerifyClientMessageRaw), + CheckForMisbehaviour(CheckForMisbehaviourMsgRaw), +} +``` + +To learn what it is expected from the Wasm light client contract when processing each message, please read the corresponsing section of the [Light client developer guide](../01-developer-guide/01-overview.md): + +- For `StatusMsg`, see the section [`Status` method](../01-developer-guide/02-client-state.md#status-method). +- For `ExportMetadataMsg`, see the section [Genesis metadata](../01-developer-guide/08-genesis.md#genesis-metadata). +- For `TimestampAtHeightMsg`, see the section [`GetTimestampAtHeight` method](../01-developer-guide/02-client-state.md#gettimestampatheight-method). +- For `VerifyClientMessageMsg`, see the section [`VerifyClientMessage`](../01-developer-guide/04-updates-and-misbehaviour.md#verifyclientmessage). +- For `CheckForMisbehaviourMsg`, see the section [`CheckForMisbehaviour` method](../01-developer-guide/02-client-state.md#checkformisbehaviour-method). + +## `SudoMsg` + +`SudoMsg` acts as a discriminated union type that is used to encode the messages that are sent to the contract's `sudo` entry point. Only one of the fields of the type should be set at a time, so that the other fields are omitted in the encoded JSON and the payload can be correctly translated to the corresponding element of the enumeration in Rust. + +The `sudo` entry point is able to perform state-changing writes in the client-prefixed store. + +```go +type SudoMsg struct { + UpdateState *UpdateStateMsg `json:"update_state,omitempty"` + UpdateStateOnMisbehaviour *UpdateStateOnMisbehaviourMsg `json:"update_state_on_misbehaviour,omitempty"` + VerifyUpgradeAndUpdateState *VerifyUpgradeAndUpdateStateMsg `json:"verify_upgrade_and_update_state,omitempty"` + VerifyMembership *VerifyMembershipMsg `json:"verify_membership,omitempty"` + VerifyNonMembership *VerifyNonMembershipMsg `json:"verify_non_membership,omitempty"` + MigrateClientStore *MigrateClientStoreMsg `json:"migrate_client_store,omitempty"` +} +``` + +```rust +#[cw_serde] +pub enum SudoMsg { + UpdateState(UpdateStateMsgRaw), + UpdateStateOnMisbehaviour(UpdateStateOnMisbehaviourMsgRaw), + VerifyUpgradeAndUpdateState(VerifyUpgradeAndUpdateStateMsgRaw), + VerifyMembership(VerifyMembershipMsgRaw), + VerifyNonMembership(VerifyNonMembershipMsgRaw), + MigrateClientStore(MigrateClientStoreMsgRaw), +} +``` + +To learn what it is expected from the Wasm light client contract when processing each message, please read the corresponsing section of the [Light client developer guide](../01-developer-guide/01-overview.md): + +- For `UpdateStateMsg`, see the section [`UpdateState`](../01-developer-guide/04-updates-and-misbehaviour.md#updatestate). +- For `UpdateStateOnMisbehaviourMsg`, see the section [`UpdateStateOnMisbehaviour`](../01-developer-guide/04-updates-and-misbehaviour.md#updatestateonmisbehaviour). +- For `VerifyUpgradeAndUpdateStateMsg`, see the section [`GetTimestampAtHeight` method](../01-developer-guide/05-upgrades.md#implementing-verifyupgradeandupdatestate). +- For `VerifyMembershipMsg`, see the section [`VerifyMembership` method](../01-developer-guide/02-client-state.md#verifymembership-method). +- For `VerifyNonMembershipMsg`, see the section [`VerifyNonMembership` method](../01-developer-guide/02-client-state.md#verifynonmembership-method). +- For `MigrateClientStoreMsg`, see the section [Implementing `CheckSubstituteAndUpdateState`](../01-developer-guide/07-proposals.md#implementing-checksubstituteandupdatestate). + +### Migration + +The `08-wasm` proxy light client exposes the `MigrateContract` RPC endpoint that can be used to migrate a given Wasm light client contract (specified by the client identifier) to a new Wasm byte code (specified by the hash of the byte code). The expected use case for this RPC endpoint is to enable contracts to migrate to new byte code in case the current byte code is found to have a bug or vulnerability. The Wasm byte code that contracts are migrated have to be uploaded beforehand using `MsgStoreCode` and must implement the `migrate` entry point. See section[`MsgMigrateContract`](./04-messages.md#msgmigratecontract) for information about the request messsage for this RPC endpoint. + +## Expected behaviour + +The `08-wasm` proxy light client modules expects the following behaviour from the Wasm light client contracts when executing messages that perform state-changing writes: + +- The contract must not delete the client state from the store. +- The contract must not change the client state to a client state of another type. +- The contract must not change the code hash in the client state. + +Any violation of these rules will result in an error returned from `08-wasm` that will abort the transaction. diff --git a/docs/docs/03-light-clients/04-wasm/08-client.md b/docs/docs/03-light-clients/04-wasm/08-client.md new file mode 100644 index 00000000000..ef49311479a --- /dev/null +++ b/docs/docs/03-light-clients/04-wasm/08-client.md @@ -0,0 +1,141 @@ +--- +title: Client +sidebar_label: Client +sidebar_position: 7 +slug: /ibc/light-clients/wasm/client +--- + +# Client + +## CLI + +A user can query and interact with the `08-wasm` module using the CLI. Use the `--help` flag to discover the available commands: + +### Transactions + +The `tx` commands allow users to interact with the `08-wasm` submodule. + +```shell +simd tx ibc-wasm --help +``` + +#### `store-code` + +The `store-code` command allows users to submit a governance proposal with a `MsgStoreCode` to store the byte code of a Wasm light client contract. + +```shell +simd tx ibc-wasm store-code [path/to/wasm-file] [flags] +``` + +`path/to/wasm-file` is the path to the `.wasm` or `.wasm.gz` file. + +### Query + +The `query` commands allow users to query `08-wasm` state. + +```shell +simd query ibc-wasm --help +``` + +#### `code-hashes` + +The `code-hashes` command allows users to query the list of code hashes of Wasm light client contracts stored in the Wasm VM via the `MsgStoreCode`. The code hashes are hex-encoded. + +```shell +simd query ibc-wasm code-hashes [flags] +``` + +Example: + +```shell +simd query ibc-wasm code-hashes +``` + +Example Output: + +```shell +code_hashes: +- c64f75091a6195b036f472cd8c9f19a56780b9eac3c3de7ced0ec2e29e985b64 +pagination: + next_key: null + total: "1" +``` + +#### `code` + +The `code` command allows users to query the Wasm byte code of a light client contract given the provided input code hash. + +```shell +./simd q ibc-wasm code +``` + +Example: + +```shell +simd query ibc-wasm code c64f75091a6195b036f472cd8c9f19a56780b9eac3c3de7ced0ec2e29e985b64 +``` + +Example Output: + +```shell +code: AGFzb...AqBBE= +``` + +## gRPC + +A user can query the `08-wasm` module using gRPC endpoints. + +### `CodeHashes` + +The `CodeHashes` endpoint allows users to query the list of code hashes of Wasm light client contracts stored in the Wasm VM via the `MsgStoreCode`. + +```shell +ibc.lightclients.wasm.v1.Query/CodeHashes +``` + +Example: + +```shell +grpcurl -plaintext \ + -d '{}' \ + localhost:9090 \ + ibc.lightclients.wasm.v1.Query/CodeHashes +``` + +Example output: + +```shell +{ + "codeIds": [ + "c64f75091a6195b036f472cd8c9f19a56780b9eac3c3de7ced0ec2e29e985b64" + ], + "pagination": { + "total": "1" + } +} +``` + +### `Code` + +The `Code` endpoint allows users to query the Wasm byte code of a light client contract given the provided input code hash. + +```shell +ibc.lightclients.wasm.v1.Query/Code +``` + +Example: + +```shell +grpcurl -plaintext \ + -d '{"code_hash":"c64f75091a6195b036f472cd8c9f19a56780b9eac3c3de7ced0ec2e29e985b64"}' \ + localhost:9090 \ + ibc.lightclients.wasm.v1.Query/Code +``` + +Example output: + +```shell +{ + "code": AGFzb...AqBBE= +} +``` diff --git a/docs/docs/03-light-clients/04-wasm/_category_.json b/docs/docs/03-light-clients/04-wasm/_category_.json new file mode 100644 index 00000000000..6dc3d24d11a --- /dev/null +++ b/docs/docs/03-light-clients/04-wasm/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Wasm", + "position": 4, + "link": null +} \ No newline at end of file diff --git a/go.work.example b/go.work.example index ba6153f3524..07249434f5a 100644 --- a/go.work.example +++ b/go.work.example @@ -4,5 +4,6 @@ use ( ./ ./modules/capability ./modules/apps/callbacks + ./modules/light-clients/08-wasm ./e2e ) diff --git a/modules/core/02-client/keeper/keeper.go b/modules/core/02-client/keeper/keeper.go index a635fbef5a7..026d60faea3 100644 --- a/modules/core/02-client/keeper/keeper.go +++ b/modules/core/02-client/keeper/keeper.go @@ -272,6 +272,7 @@ func (k Keeper) GetSelfConsensusState(ctx sdk.Context, height exported.Height) ( Root: commitmenttypes.NewMerkleRoot(histInfo.Header.GetAppHash()), NextValidatorsHash: histInfo.Header.NextValidatorsHash, } + return consensusState, nil } diff --git a/modules/core/02-client/types/params.go b/modules/core/02-client/types/params.go index 2234f049e66..dc9b70b5cbc 100644 --- a/modules/core/02-client/types/params.go +++ b/modules/core/02-client/types/params.go @@ -9,7 +9,7 @@ import ( ) // DefaultAllowedClients are the default clients for the AllowedClients parameter. -var DefaultAllowedClients = []string{exported.Solomachine, exported.Tendermint, exported.Localhost} +var DefaultAllowedClients = []string{exported.Solomachine, exported.Tendermint, exported.Wasm, exported.Localhost} // NewParams creates a new parameter configuration for the ibc client module func NewParams(allowedClients ...string) Params { diff --git a/modules/core/03-connection/keeper/verify_test.go b/modules/core/03-connection/keeper/verify_test.go index 52977dd08e9..c42fb0e044b 100644 --- a/modules/core/03-connection/keeper/verify_test.go +++ b/modules/core/03-connection/keeper/verify_test.go @@ -136,7 +136,6 @@ func (suite *KeeperTestSuite) TestVerifyClientConsensusState() { tc.malleate() connection := path.EndpointA.GetConnection() - proof, consensusHeight := suite.chainB.QueryConsensusStateProof(path.EndpointB.ClientID) proofHeight := clienttypes.NewHeight(1, uint64(suite.chainB.GetContext().BlockHeight()-1)) consensusState, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetSelfConsensusState(suite.chainA.GetContext(), consensusHeight) diff --git a/modules/core/exported/client.go b/modules/core/exported/client.go index cf5d11ca617..3a5d5fb0f31 100644 --- a/modules/core/exported/client.go +++ b/modules/core/exported/client.go @@ -22,6 +22,9 @@ const ( // Tendermint is used to indicate that the client uses the Tendermint Consensus Algorithm. Tendermint string = "07-tendermint" + // Wasm is used to indicate that the light client is a on-chain wasm program + Wasm string = "08-wasm" + // Localhost is the client type for the localhost client. Localhost string = "09-localhost" diff --git a/modules/light-clients/06-solomachine/module.go b/modules/light-clients/06-solomachine/module.go index 82aed1e2962..0f380fd2fb8 100644 --- a/modules/light-clients/06-solomachine/module.go +++ b/modules/light-clients/06-solomachine/module.go @@ -24,6 +24,12 @@ var ( // a no-op. type AppModuleBasic struct{} +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (AppModuleBasic) IsOnePerModuleType() {} + +// IsAppModule implements the appmodule.AppModule interface. +func (AppModuleBasic) IsAppModule() {} + // Name returns the solo machine module name. func (AppModuleBasic) Name() string { return ModuleName diff --git a/modules/light-clients/07-tendermint/module.go b/modules/light-clients/07-tendermint/module.go index 3a59baf10b6..3ff358884e0 100644 --- a/modules/light-clients/07-tendermint/module.go +++ b/modules/light-clients/07-tendermint/module.go @@ -24,6 +24,12 @@ var ( // a no-op. type AppModuleBasic struct{} +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (AppModuleBasic) IsOnePerModuleType() {} + +// IsAppModule implements the appmodule.AppModule interface. +func (AppModuleBasic) IsAppModule() {} + // Name returns the tendermint module name. func (AppModuleBasic) Name() string { return ModuleName diff --git a/modules/light-clients/08-wasm/Dockerfile b/modules/light-clients/08-wasm/Dockerfile new file mode 100644 index 00000000000..b32b7242bc8 --- /dev/null +++ b/modules/light-clients/08-wasm/Dockerfile @@ -0,0 +1,38 @@ +FROM golang:1.21-alpine3.18 as builder + +ARG LIBWASM_VERSION +ARG LIBWASM_CHECKSUM + +RUN test -n "${LIBWASM_VERSION}" +RUN test -n "${LIBWASM_CHECKSUM}" + +RUN set -eux; apk add --no-cache git libusb-dev linux-headers gcc musl-dev make; + +ENV GOPATH="" + +# Grab the static library and copy it to location that will be found by the linker flag `-lwasmvm_muslc`. +ADD https://github.com/CosmWasm/wasmvm/releases/download/${LIBWASM_VERSION}/libwasmvm_muslc.x86_64.a /lib/libwasmvm_muslc.x86_64.a +RUN sha256sum /lib/libwasmvm_muslc.x86_64.a | grep ${LIBWASM_CHECKSUM} +RUN cp /lib/libwasmvm_muslc.x86_64.a /lib/libwasmvm_muslc.a + +# Copy relevant files before go mod download. Replace directives to local paths break if local +# files are not copied before go mod download. +ADD internal internal +ADD testing testing +ADD modules modules +ADD LICENSE LICENSE + +COPY go.mod . +COPY go.sum . + +WORKDIR /go/modules/light-clients/08-wasm + +RUN go mod download + +RUN GOOS=linux GOARCH=amd64 go build -mod=readonly -tags "netgo ledger muslc" -ldflags '-X github.com/cosmos/cosmos-sdk/version.Name=sim -X github.com/cosmos/cosmos-sdk/version.AppName=simd -X github.com/cosmos/cosmos-sdk/version.Version= -X github.com/cosmos/cosmos-sdk/version.Commit= -X "github.com/cosmos/cosmos-sdk/version.BuildTags=netgo ledger muslc," -w -s -linkmode=external -extldflags "-Wl,-z,muldefs -static"' -trimpath -o /go/build/ ./... + +FROM alpine:3.18 + +COPY --from=builder /go/build/simd /bin/simd + +ENTRYPOINT ["simd"] diff --git a/modules/light-clients/08-wasm/client/cli/cli.go b/modules/light-clients/08-wasm/client/cli/cli.go new file mode 100644 index 00000000000..a40ea3ce48f --- /dev/null +++ b/modules/light-clients/08-wasm/client/cli/cli.go @@ -0,0 +1,42 @@ +package cli + +import ( + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" +) + +// GetQueryCmd returns the query commands for IBC channels +func GetQueryCmd() *cobra.Command { + queryCmd := &cobra.Command{ + Use: "ibc-wasm", + Short: "IBC wasm manager module query subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + queryCmd.AddCommand( + getCmdCode(), + getCmdChecksums(), + ) + + return queryCmd +} + +// NewTxCmd returns a CLI command handler for all x/ibc channel transaction commands. +func NewTxCmd() *cobra.Command { + txCmd := &cobra.Command{ + Use: "ibc-wasm", + Short: "IBC wasm manager module transaction subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + txCmd.AddCommand( + newSubmitStoreCodeProposalCmd(), + ) + + return txCmd +} diff --git a/modules/light-clients/08-wasm/client/cli/query.go b/modules/light-clients/08-wasm/client/cli/query.go new file mode 100644 index 00000000000..d5a0ebe9d97 --- /dev/null +++ b/modules/light-clients/08-wasm/client/cli/query.go @@ -0,0 +1,80 @@ +package cli + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/version" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported" +) + +// getCmdCode defines the command to query wasm code for given checksum. +func getCmdCode() *cobra.Command { + cmd := &cobra.Command{ + Use: "code [checksum]", + Short: "Query wasm code", + Long: "Query wasm code for a light client wasm contract with a given checksum", + Example: fmt.Sprintf("%s query %s wasm code [checksum]", version.AppName, ibcexported.ModuleName), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + checksum := args[0] + req := types.QueryCodeRequest{ + Checksum: checksum, + } + + res, err := queryClient.Code(context.Background(), &req) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// getCmdChecksums defines the command to query all wasm checksums. +func getCmdChecksums() *cobra.Command { + cmd := &cobra.Command{ + Use: "checksums", + Short: "Query all checksums", + Long: "Query all checksums for all deployed light client wasm contracts", + Example: fmt.Sprintf("%s query %s wasm checksums", version.AppName, ibcexported.ModuleName), + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + req := types.QueryChecksumsRequest{} + + res, err := queryClient.Checksums(context.Background(), &req) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "all wasm code") + + return cmd +} diff --git a/modules/light-clients/08-wasm/client/cli/tx.go b/modules/light-clients/08-wasm/client/cli/tx.go new file mode 100644 index 00000000000..d16523141f6 --- /dev/null +++ b/modules/light-clients/08-wasm/client/cli/tx.go @@ -0,0 +1,84 @@ +package cli + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/tx" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + "github.com/cosmos/cosmos-sdk/version" + govcli "github.com/cosmos/cosmos-sdk/x/gov/client/cli" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + types "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported" +) + +const FlagAuthority = "authority" + +// newSubmitStoreCodeProposalCmd returns the command to send a proposal to store new wasm bytecode. +func newSubmitStoreCodeProposalCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "store-code [path/to/wasm-file]", + Short: "Reads wasm code from the file and creates a proposal to store the wasm code", + Long: "Reads wasm code from the file and creates a proposal to store the wasm code", + Example: fmt.Sprintf("%s tx %s wasm [path/to/wasm_file]", version.AppName, ibcexported.ModuleName), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + proposal, err := govcli.ReadGovPropFlags(clientCtx, cmd.Flags()) + if err != nil { + return err + } + + authority, _ := cmd.Flags().GetString(FlagAuthority) + if authority != "" { + if _, err = sdk.AccAddressFromBech32(authority); err != nil { + return fmt.Errorf("invalid authority address: %w", err) + } + } else { + authority = sdk.AccAddress(address.Module(govtypes.ModuleName)).String() + } + + code, err := os.ReadFile(args[0]) + if err != nil { + return err + } + + msg := &types.MsgStoreCode{ + Signer: authority, + WasmByteCode: code, + } + + if err := msg.ValidateBasic(); err != nil { + return err + } + + if err := proposal.SetMsgs([]sdk.Msg{msg}); err != nil { + return fmt.Errorf("failed to create a store code proposal message: %w", err) + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), proposal) + }, + } + + cmd.Flags().String(FlagAuthority, "", "The address of the wasm client module authority (defaults to gov)") + + flags.AddTxFlagsToCmd(cmd) + govcli.AddGovPropFlagsToCmd(cmd) + err := cmd.MarkFlagRequired(govcli.FlagTitle) + if err != nil { + panic(err) + } + + return cmd +} diff --git a/modules/light-clients/08-wasm/doc.go b/modules/light-clients/08-wasm/doc.go new file mode 100644 index 00000000000..1c4484759b5 --- /dev/null +++ b/modules/light-clients/08-wasm/doc.go @@ -0,0 +1,8 @@ +/* +Package wasm implements a concrete ClientState, ConsensusState, +ClientMessage and types for the proxy light client module communicating +with underlying Wasm light clients. +This implementation is based off the ICS 08 specification +(https://github.com/cosmos/ibc/blob/main/spec/client/ics-008-wasm-client) +*/ +package wasm diff --git a/modules/light-clients/08-wasm/go.mod b/modules/light-clients/08-wasm/go.mod new file mode 100644 index 00000000000..8d607559ef6 --- /dev/null +++ b/modules/light-clients/08-wasm/go.mod @@ -0,0 +1,199 @@ +module github.com/cosmos/ibc-go/modules/light-clients/08-wasm + +go 1.21 + +replace github.com/cosmos/ibc-go/v8 => ../../../ + +replace github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 + +require ( + cosmossdk.io/api v0.7.2 + cosmossdk.io/client/v2 v2.0.0-beta.1 + cosmossdk.io/collections v0.4.0 + cosmossdk.io/core v0.11.0 + cosmossdk.io/errors v1.0.0 + cosmossdk.io/log v1.2.1 + cosmossdk.io/math v1.2.0 + cosmossdk.io/store v1.0.0 + cosmossdk.io/tools/confix v0.1.0 + cosmossdk.io/x/circuit v0.1.0 + cosmossdk.io/x/evidence v0.1.0 + cosmossdk.io/x/feegrant v0.1.0 + cosmossdk.io/x/tx v0.12.0 + cosmossdk.io/x/upgrade v0.1.0 + github.com/CosmWasm/wasmvm v1.4.1 + github.com/cometbft/cometbft v0.38.0 + github.com/cosmos/cosmos-db v1.0.0 + github.com/cosmos/cosmos-sdk v0.50.1 + github.com/cosmos/gogoproto v1.4.11 + github.com/cosmos/ibc-go/modules/capability v1.0.0 + github.com/cosmos/ibc-go/v8 v8.0.0 + github.com/golang/protobuf v1.5.3 + github.com/grpc-ecosystem/grpc-gateway v1.16.0 + github.com/spf13/cast v1.5.1 + github.com/spf13/cobra v1.8.0 + github.com/spf13/viper v1.17.0 + github.com/stretchr/testify v1.8.4 + google.golang.org/genproto/googleapis/api v0.0.0-20231012201019-e917dd12ba7a + google.golang.org/grpc v1.59.0 +) + +require ( + cloud.google.com/go v0.110.8 // indirect + cloud.google.com/go/compute v1.23.1 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/iam v1.1.3 // indirect + cloud.google.com/go/storage v1.30.1 // indirect + cosmossdk.io/depinject v1.0.0-alpha.4 // indirect + filippo.io/edwards25519 v1.0.0 // indirect + github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect + github.com/99designs/keyring v1.2.1 // indirect + github.com/DataDog/zstd v1.5.5 // indirect + github.com/aws/aws-sdk-go v1.44.224 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect + github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect + github.com/bits-and-blooms/bitset v1.8.0 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect + github.com/cenkalti/backoff/v4 v4.1.3 // indirect + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/chzyer/readline v1.5.1 // indirect + github.com/cockroachdb/apd/v2 v2.0.2 // indirect + github.com/cockroachdb/errors v1.11.1 // indirect + github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect + github.com/cockroachdb/pebble v0.0.0-20231102162011-844f0582c2eb // indirect + github.com/cockroachdb/redact v1.1.5 // indirect + github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect + github.com/cometbft/cometbft-db v0.8.0 // indirect + github.com/cosmos/btcutil v1.0.5 // indirect + github.com/cosmos/cosmos-proto v1.0.0-beta.3 // indirect + github.com/cosmos/go-bip39 v1.0.0 // indirect + github.com/cosmos/gogogateway v1.2.0 // indirect + github.com/cosmos/iavl v1.0.0 // indirect + github.com/cosmos/ics23/go v0.10.0 // indirect + github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect + github.com/creachadair/atomicfile v0.3.1 // indirect + github.com/creachadair/tomledit v0.0.24 // indirect + github.com/danieljoos/wincred v1.1.2 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect + github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect + github.com/dgraph-io/badger/v2 v2.2007.4 // indirect + github.com/dgraph-io/ristretto v0.1.1 // indirect + github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/dvsekhvalnov/jose2go v1.5.0 // indirect + github.com/emicklei/dot v1.6.0 // indirect + github.com/fatih/color v1.15.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/getsentry/sentry-go v0.25.0 // indirect + github.com/go-kit/kit v0.12.0 // indirect + github.com/go-kit/log v0.2.1 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect + github.com/gogo/googleapis v1.4.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/glog v1.1.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/mock v1.6.0 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/btree v1.1.2 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/orderedcode v0.0.1 // indirect + github.com/google/s2a-go v0.1.7 // indirect + github.com/google/uuid v1.3.1 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.1 // indirect + github.com/googleapis/gax-go/v2 v2.12.0 // indirect + github.com/gorilla/handlers v1.5.2 // indirect + github.com/gorilla/mux v1.8.1 // indirect + github.com/gorilla/websocket v1.5.0 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect + github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-getter v1.7.1 // indirect + github.com/hashicorp/go-hclog v1.5.0 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-metrics v0.5.2 // indirect + github.com/hashicorp/go-plugin v1.5.2 // indirect + github.com/hashicorp/go-safetemp v1.0.0 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/hashicorp/yamux v0.1.1 // indirect + github.com/hdevalence/ed25519consensus v0.1.0 // indirect + github.com/huandu/skiplist v1.2.0 // indirect + github.com/iancoleman/strcase v0.3.0 // indirect + github.com/improbable-eng/grpc-web v0.15.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/jmhodges/levigo v1.0.0 // indirect + github.com/klauspost/compress v1.17.2 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/lib/pq v1.10.7 // indirect + github.com/libp2p/go-buffer-pool v0.1.0 // indirect + github.com/linxGnu/grocksdb v1.8.4 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/manifoldco/promptui v0.9.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect + github.com/minio/highwayhash v1.0.2 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mtibben/percent v0.2.1 // indirect + github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect + github.com/oklog/run v1.1.0 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/petermattis/goid v0.0.0-20230904192822-1876fd5063bc // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.17.0 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/rs/cors v1.8.3 // indirect + github.com/rs/zerolog v1.31.0 // indirect + github.com/sagikazarmark/locafero v0.3.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sasha-s/go-deadlock v0.3.1 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.10.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect + github.com/tendermint/go-amino v0.16.0 // indirect + github.com/tidwall/btree v1.7.0 // indirect + github.com/ulikunitz/xz v0.5.11 // indirect + github.com/zondax/hid v0.9.2 // indirect + github.com/zondax/ledger-go v0.14.3 // indirect + go.etcd.io/bbolt v1.3.7 // indirect + go.opencensus.io v0.24.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/oauth2 v0.12.0 // indirect + golang.org/x/sync v0.5.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + google.golang.org/api v0.143.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gotest.tools/v3 v3.5.1 // indirect + nhooyr.io/websocket v1.8.6 // indirect + pgregory.net/rapid v1.1.0 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) diff --git a/modules/light-clients/08-wasm/go.sum b/modules/light-clients/08-wasm/go.sum new file mode 100644 index 00000000000..d915f9de8c2 --- /dev/null +++ b/modules/light-clients/08-wasm/go.sum @@ -0,0 +1,1714 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= +cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= +cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= +cloud.google.com/go v0.110.8 h1:tyNdfIxjzaWctIiLYOTalaLKZ17SI44SKFW26QbOhME= +cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk= +cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= +cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= +cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= +cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= +cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= +cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= +cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= +cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= +cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= +cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= +cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= +cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= +cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= +cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= +cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= +cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= +cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= +cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= +cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= +cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= +cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= +cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= +cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= +cloud.google.com/go/compute v1.23.1 h1:V97tBoDaZHb6leicZ1G6DLK2BAaZLJ/7+9BB/En3hR0= +cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= +cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= +cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= +cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= +cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= +cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= +cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= +cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= +cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= +cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= +cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= +cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= +cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= +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/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= +cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= +cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= +cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= +cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= +cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= +cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= +cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= +cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= +cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= +cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= +cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= +cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= +cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= +cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= +cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= +cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= +cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= +cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= +cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= +cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= +cloud.google.com/go/iam v1.1.3 h1:18tKG7DzydKWUnLjonWcJO6wjSCAtzh4GcRKlH/Hrzc= +cloud.google.com/go/iam v1.1.3/go.mod h1:3khUlaBXfPKKe7huYgEpDn6FtgRyMEqbkvBxrQyY5SE= +cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= +cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= +cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= +cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= +cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= +cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= +cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= +cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= +cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= +cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= +cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= +cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= +cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= +cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= +cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= +cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= +cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= +cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= +cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= +cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= +cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= +cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= +cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= +cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= +cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= +cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= +cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= +cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= +cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= +cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= +cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= +cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= +cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= +cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= +cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= +cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= +cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= +cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= +cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= +cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= +cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= +cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= +cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= +cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= +cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= +cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= +cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= +cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= +cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= +cloud.google.com/go/storage v1.30.1 h1:uOdMxAs8HExqBlnLtnQyP0YkvbiDpdGShGKtx6U/oNM= +cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= +cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= +cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= +cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= +cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= +cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= +cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= +cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= +cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= +cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= +cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= +cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= +cosmossdk.io/api v0.7.2 h1:BO3i5fvKMKvfaUiMkCznxViuBEfyWA/k6w2eAF6q1C4= +cosmossdk.io/api v0.7.2/go.mod h1:IcxpYS5fMemZGqyYtErK7OqvdM0C8kdW3dq8Q/XIG38= +cosmossdk.io/client/v2 v2.0.0-beta.1 h1:XkHh1lhrLYIT9zKl7cIOXUXg2hdhtjTPBUfqERNA1/Q= +cosmossdk.io/client/v2 v2.0.0-beta.1/go.mod h1:JEUSu9moNZQ4kU3ir1DKD5eU4bllmAexrGWjmb9k8qU= +cosmossdk.io/collections v0.4.0 h1:PFmwj2W8szgpD5nOd8GWH6AbYNi1f2J6akWXJ7P5t9s= +cosmossdk.io/collections v0.4.0/go.mod h1:oa5lUING2dP+gdDquow+QjlF45eL1t4TJDypgGd+tv0= +cosmossdk.io/core v0.11.0 h1:vtIafqUi+1ZNAE/oxLOQQ7Oek2n4S48SWLG8h/+wdbo= +cosmossdk.io/core v0.11.0/go.mod h1:LaTtayWBSoacF5xNzoF8tmLhehqlA9z1SWiPuNC6X1w= +cosmossdk.io/depinject v1.0.0-alpha.4 h1:PLNp8ZYAMPTUKyG9IK2hsbciDWqna2z1Wsl98okJopc= +cosmossdk.io/depinject v1.0.0-alpha.4/go.mod h1:HeDk7IkR5ckZ3lMGs/o91AVUc7E596vMaOmslGFM3yU= +cosmossdk.io/errors v1.0.0 h1:nxF07lmlBbB8NKQhtJ+sJm6ef5uV1XkvPXG2bUntb04= +cosmossdk.io/errors v1.0.0/go.mod h1:+hJZLuhdDE0pYN8HkOrVNwrIOYvUGnn6+4fjnJs/oV0= +cosmossdk.io/log v1.2.1 h1:Xc1GgTCicniwmMiKwDxUjO4eLhPxoVdI9vtMW8Ti/uk= +cosmossdk.io/log v1.2.1/go.mod h1:GNSCc/6+DhFIj1aLn/j7Id7PaO8DzNylUZoOYBL9+I4= +cosmossdk.io/math v1.2.0 h1:8gudhTkkD3NxOP2YyyJIYYmt6dQ55ZfJkDOaxXpy7Ig= +cosmossdk.io/math v1.2.0/go.mod h1:l2Gnda87F0su8a/7FEKJfFdJrM0JZRXQaohlgJeyQh0= +cosmossdk.io/store v1.0.0 h1:6tnPgTpTSIskaTmw/4s5C9FARdgFflycIc9OX8i1tOI= +cosmossdk.io/store v1.0.0/go.mod h1:ABMprwjvx6IpMp8l06TwuMrj6694/QP5NIW+X6jaTYc= +cosmossdk.io/tools/confix v0.1.0 h1:2OOZTtQsDT5e7P3FM5xqM0bPfluAxZlAwxqaDmYBE+E= +cosmossdk.io/tools/confix v0.1.0/go.mod h1:TdXKVYs4gEayav5wM+JHT+kTU2J7fozFNqoVaN+8CdY= +cosmossdk.io/x/circuit v0.1.0 h1:IAej8aRYeuOMritczqTlljbUVHq1E85CpBqaCTwYgXs= +cosmossdk.io/x/circuit v0.1.0/go.mod h1:YDzblVE8+E+urPYQq5kq5foRY/IzhXovSYXb4nwd39w= +cosmossdk.io/x/evidence v0.1.0 h1:J6OEyDl1rbykksdGynzPKG5R/zm6TacwW2fbLTW4nCk= +cosmossdk.io/x/evidence v0.1.0/go.mod h1:hTaiiXsoiJ3InMz1uptgF0BnGqROllAN8mwisOMMsfw= +cosmossdk.io/x/feegrant v0.1.0 h1:c7s3oAq/8/UO0EiN1H5BIjwVntujVTkYs35YPvvrdQk= +cosmossdk.io/x/feegrant v0.1.0/go.mod h1:4r+FsViJRpcZif/yhTn+E0E6OFfg4n0Lx+6cCtnZElU= +cosmossdk.io/x/tx v0.12.0 h1:Ry2btjQdrfrje9qZ3iZeZSmDArjgxUJMMcLMrX4wj5U= +cosmossdk.io/x/tx v0.12.0/go.mod h1:qTth2coAGkwCwOCjqQ8EAQg+9udXNRzcnSbMgGKGEI0= +cosmossdk.io/x/upgrade v0.1.0 h1:z1ZZG4UL9ICTNbJDYZ6jOnF9GdEK9wyoEFi4BUScHXE= +cosmossdk.io/x/upgrade v0.1.0/go.mod h1:/6jjNGbiPCNtmA1N+rBtP601sr0g4ZXuj3yC6ClPCGY= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= +filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= +github.com/99designs/keyring v1.2.1 h1:tYLp1ULvO7i3fI5vE21ReQuj99QFSs7lGm0xWyJo87o= +github.com/99designs/keyring v1.2.1/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/CosmWasm/wasmvm v1.4.1 h1:YgodVlBrXa2HJZzOXjWDH0EIRwQzK3zuA73dDPRRLS4= +github.com/CosmWasm/wasmvm v1.4.1/go.mod h1:fXB+m2gyh4v9839zlIXdMZGeLAxqUdYdFQqYsTha2hc= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= +github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= +github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= +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= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/adlio/schema v1.3.3 h1:oBJn8I02PyTB466pZO1UZEn1TV5XLlifBSyMrmHl/1I= +github.com/adlio/schema v1.3.3/go.mod h1:1EsRssiv9/Ce2CMzq5DoL7RiMshhuigQxrR4DMV9fHg= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/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-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.224 h1:09CiaaF35nRmxrzWZ2uRq5v6Ghg/d2RiPjZnSgtt+RQ= +github.com/aws/aws-sdk-go v1.44.224/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 h1:41iFGWnSlI2gVpmOtVTJZNodLdLQLn/KsJqFvXwnd/s= +github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c= +github.com/bits-and-blooms/bitset v1.8.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= +github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ= +github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/bufbuild/protocompile v0.6.0 h1:Uu7WiSQ6Yj9DbkdnOe7U4mNKp58y9WDMKDn28/ZlunY= +github.com/bufbuild/protocompile v0.6.0/go.mod h1:YNP35qEYoYGme7QMtz5SBCoN4kL4g12jTtjuzRNdjpE= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= +github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/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/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E= +github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8= +github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/pebble v0.0.0-20231102162011-844f0582c2eb h1:6Po+YYKT5B5ZXN0wd2rwFBaebM0LufPf8p4zxOd48Kg= +github.com/cockroachdb/pebble v0.0.0-20231102162011-844f0582c2eb/go.mod h1:acMRUGd/BK8AUmQNK3spUCCGzFLZU2bSST3NMXSq2Kc= +github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= +github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/cometbft/cometbft v0.38.0 h1:ogKnpiPX7gxCvqTEF4ly25/wAxUqf181t30P3vqdpdc= +github.com/cometbft/cometbft v0.38.0/go.mod h1:5Jz0Z8YsHSf0ZaAqGvi/ifioSdVFPtEGrm8Y9T/993k= +github.com/cometbft/cometbft-db v0.8.0 h1:vUMDaH3ApkX8m0KZvOFFy9b5DZHBAjsnEuo9AKVZpjo= +github.com/cometbft/cometbft-db v0.8.0/go.mod h1:6ASCP4pfhmrCBpfk01/9E1SI29nD3HfVHrY4PG8x5c0= +github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= +github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= +github.com/coreos/etcd v3.3.10+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-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cosmos/btcutil v1.0.5 h1:t+ZFcX77LpKtDBhjucvnOH8C2l2ioGsBNEQ3jef8xFk= +github.com/cosmos/btcutil v1.0.5/go.mod h1:IyB7iuqZMJlthe2tkIFL33xPyzbFYP0XVdS8P5lUPis= +github.com/cosmos/cosmos-db v1.0.0 h1:EVcQZ+qYag7W6uorBKFPvX6gRjw6Uq2hIh4hCWjuQ0E= +github.com/cosmos/cosmos-db v1.0.0/go.mod h1:iBvi1TtqaedwLdcrZVYRSSCb6eSy61NLj4UNmdIgs0U= +github.com/cosmos/cosmos-proto v1.0.0-beta.3 h1:VitvZ1lPORTVxkmF2fAp3IiA61xVwArQYKXTdEcpW6o= +github.com/cosmos/cosmos-proto v1.0.0-beta.3/go.mod h1:t8IASdLaAq+bbHbjq4p960BvcTqtwuAxid3b/2rOD6I= +github.com/cosmos/cosmos-sdk v0.50.1 h1:2SYwAYqd7ZwtrWxu/J8PwbQV/cDcu90bCr/a78g3lVw= +github.com/cosmos/cosmos-sdk v0.50.1/go.mod h1:fsLSPGstCwn6MMsFDMAQWGJj8E4sYsN9Gnu1bGE5imA= +github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= +github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= +github.com/cosmos/gogogateway v1.2.0 h1:Ae/OivNhp8DqBi/sh2A8a1D0y638GpL3tkmLQAiKxTE= +github.com/cosmos/gogogateway v1.2.0/go.mod h1:iQpLkGWxYcnCdz5iAdLcRBSw3h7NXeOkZ4GUkT+tbFI= +github.com/cosmos/gogoproto v1.4.2/go.mod h1:cLxOsn1ljAHSV527CHOtaIP91kK6cCrZETRBrkzItWU= +github.com/cosmos/gogoproto v1.4.11 h1:LZcMHrx4FjUgrqQSWeaGC1v/TeuVFqSLa43CC6aWR2g= +github.com/cosmos/gogoproto v1.4.11/go.mod h1:/g39Mh8m17X8Q/GDEs5zYTSNaNnInBSohtaxzQnYq1Y= +github.com/cosmos/iavl v1.0.0 h1:bw6t0Mv/mVCJvlMTOPHWLs5uUE3BRBfVWCRelOzl+so= +github.com/cosmos/iavl v1.0.0/go.mod h1:CmTGqMnRnucjxbjduneZXT+0vPgNElYvdefjX2q9tYc= +github.com/cosmos/ibc-go/modules/capability v1.0.0 h1:r/l++byFtn7jHYa09zlAdSeevo8ci1mVZNO9+V0xsLE= +github.com/cosmos/ibc-go/modules/capability v1.0.0/go.mod h1:D81ZxzjZAe0ZO5ambnvn1qedsFQ8lOwtqicG6liLBco= +github.com/cosmos/ics23/go v0.10.0 h1:iXqLLgp2Lp+EdpIuwXTYIQU+AiHj9mOC2X9ab++bZDM= +github.com/cosmos/ics23/go v0.10.0/go.mod h1:ZfJSmng/TBNTBkFemHHHj5YY7VAU/MBU980F4VU1NG0= +github.com/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= +github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8= +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.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creachadair/atomicfile v0.3.1 h1:yQORkHjSYySh/tv5th1dkKcn02NEW5JleB84sjt+W4Q= +github.com/creachadair/atomicfile v0.3.1/go.mod h1:mwfrkRxFKwpNAflYZzytbSwxvbK6fdGRRlp0KEQc0qU= +github.com/creachadair/tomledit v0.0.24 h1:5Xjr25R2esu1rKCbQEmjZYlrhFkDspoAbAKb6QKQDhQ= +github.com/creachadair/tomledit v0.0.24/go.mod h1:9qHbShRWQzSCcn617cMzg4eab1vbLCOjOshAWSzWr8U= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= +github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +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.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= +github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= +github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM= +github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/emicklei/dot v1.6.0 h1:vUzuoVE8ipzS7QkES4UfxdpCwdU2U97m2Pb2tQCoYRY= +github.com/emicklei/dot v1.6.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/getsentry/sentry-go v0.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI= +github.com/getsentry/sentry-go v0.25.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= +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/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= +github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-kit/kit v0.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.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= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= +github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/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= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= +github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= +github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.4.1-0.20201022092350-68b0159b7869/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= +github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= +github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= +github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= +github.com/google/martian/v3 v3.3.2/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= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/enterprise-certificate-proxy v0.3.1 h1:SBWmZhjUDRorQxrN0nwzf+AHBxnbFjViHQS4P0yVpmQ= +github.com/googleapis/enterprise-certificate-proxy v0.3.1/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= +github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= +github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= +github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-getter v1.7.1 h1:SWiSWN/42qdpR0MdhaOc/bLR48PLuP1ZQtYLRlM69uY= +github.com/hashicorp/go-getter v1.7.1/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-metrics v0.5.2 h1:ErEYO2f//CjKsUDw4SmLzelsK6L3ZmOAR/4P9iS7ruY= +github.com/hashicorp/go-metrics v0.5.2/go.mod h1:KEjodfebIOuBYSAe/bHTm+HChmKSxAOXPBieMLYozDE= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-plugin v1.5.2 h1:aWv8eimFqWlsEiMrYZdPYl+FdHaBJSN4AWwGWfT1G2Y= +github.com/hashicorp/go-plugin v1.5.2/go.mod h1:w1sAEES3g3PuV/RzUrgow20W2uErMly84hhD3um1WL4= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= +github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/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/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= +github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/hdevalence/ed25519consensus v0.1.0 h1:jtBwzzcHuTmFrQN6xQZn6CQEO/V9f7HsjsjeEZ6auqU= +github.com/hdevalence/ed25519consensus v0.1.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c= +github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= +github.com/huandu/skiplist v1.2.0 h1:gox56QD77HzSC0w+Ws3MH3iie755GBJU1OER3h5VsYw= +github.com/huandu/skiplist v1.2.0/go.mod h1:7v3iFjLcSAzO4fN5B8dvebvo/qsfumiLiDXMrPiHF9w= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= +github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jhump/protoreflect v1.15.3 h1:6SFRuqU45u9hIZPJAoZ8c28T3nK64BNdp9w6jFonzls= +github.com/jhump/protoreflect v1.15.3/go.mod h1:4ORHmSBmlCW8fh3xHmJMGyul1zNqZK4Elxc8qKP+p1k= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= +github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/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.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= +github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +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.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= +github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= +github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/linxGnu/grocksdb v1.8.4 h1:ZMsBpPpJNtRLHiKKp0mI7gW+NT4s7UgfD5xHxx1jVRo= +github.com/linxGnu/grocksdb v1.8.4/go.mod h1:xZCIb5Muw+nhbDK4Y5UJuOrin5MceOuiXkVUR7vp4WY= +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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= +github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +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/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/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= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/grpc-proxy v0.0.0-20181017164139-0f1106ef9c76/go.mod h1:x5OoJHDHqxHS801UIuhqGl6QdSAEJvtausosHSdazIo= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a h1:dlRvE5fWabOchtH7znfiFCcOvmIYgOeAS5ifBXBlh9Q= +github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= +github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= +github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= +github.com/opencontainers/runc v1.1.3 h1:vIXrkId+0/J2Ymu2m7VjGvbSlAId9XNRPhn2p4b+d8w= +github.com/opencontainers/runc v1.1.3/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= +github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= +github.com/petermattis/goid v0.0.0-20230904192822-1876fd5063bc h1:8bQZVK1X6BJR/6nYUPxQEP+ReTsceJTKizeuwjWOPUA= +github.com/petermattis/goid v0.0.0-20230904192822-1876fd5063bc/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +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/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/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.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/cors v1.8.3 h1:O+qNyWn7Z+F9M0ILBHgMVPuB1xTOucVd5gtaYyXBpRo= +github.com/rs/cors v1.8.3/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= +github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +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/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ= +github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +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/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/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.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +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.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +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.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= +github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= +github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= +github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI= +github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/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= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= +github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= +github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= +github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw= +github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= +go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/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/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +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= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= +golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= +golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= +google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= +google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= +google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= +google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= +google.golang.org/api v0.143.0 h1:o8cekTkqhywkbZT6p1UHJPZ9+9uuCAJs/KYomxZB8fA= +google.golang.org/api v0.143.0/go.mod h1:FoX9DO9hT7DLNn97OuoZAGSDuNAXdJRuGK98rSUgurk= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= +google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= +google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= +google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= +google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA= +google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= +google.golang.org/genproto/googleapis/api v0.0.0-20231012201019-e917dd12ba7a h1:myvhA4is3vrit1a6NZCWBIwN0kNEnX21DJOJX/NvIfI= +google.golang.org/genproto/googleapis/api v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:SUBoKXbI1Efip18FClrQVGjWcyd0QZd8KkvdP34t7ww= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 h1:AB/lmRny7e2pLhFEYIbl5qkDAUt2h0ZRO4wGPhZf+ik= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.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-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k= +nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= +pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw= +pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +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.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/modules/light-clients/08-wasm/internal/ibcwasm/expected_interfaces.go b/modules/light-clients/08-wasm/internal/ibcwasm/expected_interfaces.go new file mode 100644 index 00000000000..45849de1e0a --- /dev/null +++ b/modules/light-clients/08-wasm/internal/ibcwasm/expected_interfaces.go @@ -0,0 +1,109 @@ +package ibcwasm + +import ( + wasmvm "github.com/CosmWasm/wasmvm" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" +) + +var _ WasmEngine = (*wasmvm.VM)(nil) + +type WasmEngine interface { + // StoreCode will compile the wasm code, and store the resulting pre-compile + // as well as the original code. Both can be referenced later via checksum + // This must be done one time for given code, after which it can be + // instatitated many times, and each instance called many times. + // It does the same as StoreCodeUnchecked plus the static checks. + StoreCode(code wasmvm.WasmCode) (wasmvm.Checksum, error) + + // Instantiate will create a new contract based on the given checksum. + // We can set the initMsg (contract "genesis") here, and it then receives + // an account and address and can be invoked (Execute) many times. + // + // Storage should be set with a PrefixedKVStore that this code can safely access. + // + // Under the hood, we may recompile the wasm, use a cached native compile, or even use a cached instance + // for performance. + Instantiate( + checksum wasmvm.Checksum, + env wasmvmtypes.Env, + info wasmvmtypes.MessageInfo, + initMsg []byte, + store wasmvm.KVStore, + goapi wasmvm.GoAPI, + querier wasmvm.Querier, + gasMeter wasmvm.GasMeter, + gasLimit uint64, + deserCost wasmvmtypes.UFraction, + ) (*wasmvmtypes.Response, uint64, error) + + // Query allows a client to execute a contract-specific query. If the result is not empty, it should be + // valid json-encoded data to return to the client. + // The meaning of path and data can be determined by the code. Path is the suffix of the abci.QueryRequest.Path + Query( + checksum wasmvm.Checksum, + env wasmvmtypes.Env, + queryMsg []byte, + store wasmvm.KVStore, + goapi wasmvm.GoAPI, + querier wasmvm.Querier, + gasMeter wasmvm.GasMeter, + gasLimit uint64, + deserCost wasmvmtypes.UFraction, + ) ([]byte, uint64, error) + + // Migrate migrates an existing contract to a new code binary. + // This takes storage of the data from the original contract and the checksum of the new contract that should + // replace it. This allows it to run a migration step if needed, or return an error if unable to migrate + // the given data. + // + // MigrateMsg has some data on how to perform the migration. + Migrate( + checksum wasmvm.Checksum, + env wasmvmtypes.Env, + migrateMsg []byte, + store wasmvm.KVStore, + goapi wasmvm.GoAPI, + querier wasmvm.Querier, + gasMeter wasmvm.GasMeter, + gasLimit uint64, + deserCost wasmvmtypes.UFraction, + ) (*wasmvmtypes.Response, uint64, error) + + // Sudo allows native Go modules to make priviledged (sudo) calls on the contract. + // The contract can expose entry points that cannot be triggered by any transaction, but only via + // native Go modules, and delegate the access control to the system. + // + // These work much like Migrate (same scenario) but allows custom apps to extend the priviledged entry points + // without forking cosmwasm-vm. + Sudo( + checksum wasmvm.Checksum, + env wasmvmtypes.Env, + sudoMsg []byte, + store wasmvm.KVStore, + goapi wasmvm.GoAPI, + querier wasmvm.Querier, + gasMeter wasmvm.GasMeter, + gasLimit uint64, + deserCost wasmvmtypes.UFraction, + ) (*wasmvmtypes.Response, uint64, error) + + // GetCode will load the original wasm code for the given checksum. + // This will only succeed if that checksum was previously returned from + // a call to Create. + // + // This can be used so that the (short) checksum is stored in the iavl tree + // and the larger binary blobs (wasm and pre-compiles) are all managed by the + // rust library + GetCode(checksum wasmvm.Checksum) (wasmvm.WasmCode, error) + + // Pin pins a code to an in-memory cache, such that is + // always loaded quickly when executed. + // Pin is idempotent. + Pin(checksum wasmvm.Checksum) error + + // Unpin removes the guarantee of a contract to be pinned (see Pin). + // After calling this, the code may or may not remain in memory depending on + // the implementor's choice. + // Unpin is idempotent. + Unpin(checksum wasmvm.Checksum) error +} diff --git a/modules/light-clients/08-wasm/internal/ibcwasm/wasm.go b/modules/light-clients/08-wasm/internal/ibcwasm/wasm.go new file mode 100644 index 00000000000..54ce26fd80c --- /dev/null +++ b/modules/light-clients/08-wasm/internal/ibcwasm/wasm.go @@ -0,0 +1,47 @@ +package ibcwasm + +import ( + "errors" + + "cosmossdk.io/collections" + storetypes "cosmossdk.io/core/store" +) + +var ( + vm WasmEngine + + // state management + Schema collections.Schema + Checksums collections.KeySet[[]byte] + + // ChecksumsKey is the key under which all checksums are stored + ChecksumsKey = collections.NewPrefix(0) +) + +// SetVM sets the wasm VM for the 08-wasm module. +// It panics if the wasm VM is nil. +func SetVM(wasmVM WasmEngine) { + if wasmVM == nil { + panic(errors.New("wasm VM must be not nil")) + } + vm = wasmVM +} + +// GetVM returns the wasm VM for the 08-wasm module. +func GetVM() WasmEngine { + return vm +} + +// SetupWasmStoreService sets up the 08-wasm module's collections. +func SetupWasmStoreService(storeService storetypes.KVStoreService) { + sb := collections.NewSchemaBuilder(storeService) + + Checksums = collections.NewKeySet(sb, ChecksumsKey, "checksums", collections.BytesKey) + + schema, err := sb.Build() + if err != nil { + panic(err) + } + + Schema = schema +} diff --git a/modules/light-clients/08-wasm/keeper/events.go b/modules/light-clients/08-wasm/keeper/events.go new file mode 100644 index 00000000000..2ce9a06ab34 --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/events.go @@ -0,0 +1,39 @@ +package keeper + +import ( + "encoding/hex" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" +) + +// emitStoreWasmCodeEvent emits a store wasm code event +func emitStoreWasmCodeEvent(ctx sdk.Context, checksum []byte) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeStoreWasmCode, + sdk.NewAttribute(types.AttributeKeyWasmChecksum, hex.EncodeToString(checksum)), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// emitMigrateContractEvent emits a migrate contract event +func emitMigrateContractEvent(ctx sdk.Context, clientID string, checksum, newChecksum []byte) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeMigrateContract, + sdk.NewAttribute(types.AttributeKeyClientID, clientID), + sdk.NewAttribute(types.AttributeKeyWasmChecksum, hex.EncodeToString(checksum)), + sdk.NewAttribute(types.AttributeKeyNewChecksum, hex.EncodeToString(newChecksum)), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} diff --git a/modules/light-clients/08-wasm/keeper/export_test.go b/modules/light-clients/08-wasm/keeper/export_test.go new file mode 100644 index 00000000000..6646fe3180a --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/export_test.go @@ -0,0 +1,9 @@ +package keeper + +/* + This file is to allow for unexported functions to be accessible to the testing package. +*/ + +func GenerateWasmChecksum(code []byte) []byte { + return generateWasmChecksum(code) +} diff --git a/modules/light-clients/08-wasm/keeper/genesis.go b/modules/light-clients/08-wasm/keeper/genesis.go new file mode 100644 index 00000000000..de88b457cb2 --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/genesis.go @@ -0,0 +1,44 @@ +package keeper + +import ( + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" +) + +// InitGenesis initializes the 08-wasm module's state from a provided genesis +// state. +func (k Keeper) InitGenesis(ctx sdk.Context, gs types.GenesisState) error { + for _, contract := range gs.Contracts { + _, err := k.storeWasmCode(ctx, contract.CodeBytes) + if err != nil { + return err + } + } + return nil +} + +// ExportGenesis returns the 08-wasm module's exported genesis. This includes the code +// for all contracts previously stored. +func (k Keeper) ExportGenesis(ctx sdk.Context) types.GenesisState { + checksums, err := types.GetAllChecksums(ctx) + if err != nil { + panic(err) + } + + // Grab code from wasmVM and add to genesis state. + var genesisState types.GenesisState + for _, checksum := range checksums { + code, err := k.wasmVM.GetCode(wasmvmtypes.Checksum(checksum)) + if err != nil { + panic(err) + } + genesisState.Contracts = append(genesisState.Contracts, types.Contract{ + CodeBytes: code, + }) + } + + return genesisState +} diff --git a/modules/light-clients/08-wasm/keeper/genesis_test.go b/modules/light-clients/08-wasm/keeper/genesis_test.go new file mode 100644 index 00000000000..20d8f3d9303 --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/genesis_test.go @@ -0,0 +1,91 @@ +package keeper_test + +import ( + "encoding/hex" + "os" + + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" +) + +func (suite *KeeperTestSuite) TestInitGenesis() { + var ( + genesisState types.GenesisState + expChecksums []string + ) + + testCases := []struct { + name string + malleate func() + }{ + { + "success", + func() { + checksum := "9b18dc4aa6a4dc6183f148bdcadbf7d3de2fdc7aac59394f1589b81e77de5e3c" //nolint:gosec // these are not hard-coded credentials + contractCode, err := os.ReadFile("../test_data/ics07_tendermint_cw.wasm.gz") + suite.Require().NoError(err) + + genesisState = *types.NewGenesisState( + []types.Contract{ + { + CodeBytes: contractCode, + }, + }, + ) + + expChecksums = []string{checksum} + }, + }, + { + "success with empty genesis contract", + func() { + genesisState = *types.NewGenesisState([]types.Contract{}) + expChecksums = []string{} + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + ctx := suite.chainA.GetContext() + tc.malleate() + + err := GetSimApp(suite.chainA).WasmClientKeeper.InitGenesis(ctx, genesisState) + suite.Require().NoError(err) + + var storedHashes []string + checksums, err := types.GetAllChecksums(suite.chainA.GetContext()) + suite.Require().NoError(err) + + for _, hash := range checksums { + storedHashes = append(storedHashes, hex.EncodeToString(hash)) + } + + suite.Require().Equal(len(expChecksums), len(storedHashes)) + suite.Require().ElementsMatch(expChecksums, storedHashes) + }) + } +} + +func (suite *KeeperTestSuite) TestExportGenesis() { + suite.SetupTest() + ctx := suite.chainA.GetContext() + + expChecksum := "9b18dc4aa6a4dc6183f148bdcadbf7d3de2fdc7aac59394f1589b81e77de5e3c" //nolint:gosec // these are not hard-coded credentials + + signer := authtypes.NewModuleAddress(govtypes.ModuleName).String() + contractCode, err := os.ReadFile("../test_data/ics07_tendermint_cw.wasm.gz") + suite.Require().NoError(err) + + msg := types.NewMsgStoreCode(signer, contractCode) + res, err := GetSimApp(suite.chainA).WasmClientKeeper.StoreCode(ctx, msg) + suite.Require().NoError(err) + suite.Require().Equal(expChecksum, hex.EncodeToString(res.Checksum)) + + genesisState := GetSimApp(suite.chainA).WasmClientKeeper.ExportGenesis(ctx) + suite.Require().Len(genesisState.Contracts, 1) + suite.Require().NotEmpty(genesisState.Contracts[0].CodeBytes) +} diff --git a/modules/light-clients/08-wasm/keeper/grpc_query.go b/modules/light-clients/08-wasm/keeper/grpc_query.go new file mode 100644 index 00000000000..7332246445d --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/grpc_query.go @@ -0,0 +1,65 @@ +package keeper + +import ( + "context" + "encoding/hex" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "cosmossdk.io/collections" + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkquery "github.com/cosmos/cosmos-sdk/types/query" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" +) + +var _ types.QueryServer = (*Keeper)(nil) + +// Code implements the Query/Code gRPC method +func (k Keeper) Code(goCtx context.Context, req *types.QueryCodeRequest) (*types.QueryCodeResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + checksum, err := hex.DecodeString(req.Checksum) + if err != nil { + return nil, status.Error(codes.InvalidArgument, "invalid checksum") + } + + // Only return checksums we previously stored, not arbitrary checksums that might be stored via e.g Wasmd. + if !types.HasChecksum(sdk.UnwrapSDKContext(goCtx), checksum) { + return nil, status.Error(codes.NotFound, errorsmod.Wrap(types.ErrWasmChecksumNotFound, req.Checksum).Error()) + } + + code, err := k.wasmVM.GetCode(checksum) + if err != nil { + return nil, status.Error(codes.NotFound, errorsmod.Wrap(types.ErrWasmChecksumNotFound, req.Checksum).Error()) + } + + return &types.QueryCodeResponse{ + Data: code, + }, nil +} + +// Checksums implements the Query/Checksums gRPC method. It returns a list of hex encoded checksums stored. +func (Keeper) Checksums(goCtx context.Context, req *types.QueryChecksumsRequest) (*types.QueryChecksumsResponse, error) { + checksums, pageRes, err := sdkquery.CollectionPaginate( + goCtx, + ibcwasm.Checksums, + req.Pagination, + func(key []byte, value collections.NoValue) (string, error) { + return hex.EncodeToString(key), nil + }) + if err != nil { + return nil, err + } + + return &types.QueryChecksumsResponse{ + Checksums: checksums, + Pagination: pageRes, + }, nil +} diff --git a/modules/light-clients/08-wasm/keeper/grpc_query_test.go b/modules/light-clients/08-wasm/keeper/grpc_query_test.go new file mode 100644 index 00000000000..3c060e0dcad --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/grpc_query_test.go @@ -0,0 +1,122 @@ +package keeper_test + +import ( + "encoding/hex" + "os" + + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" +) + +func (suite *KeeperTestSuite) TestQueryCode() { + var req *types.QueryCodeRequest + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "success", + func() { + signer := authtypes.NewModuleAddress(govtypes.ModuleName).String() + code, err := os.ReadFile("../test_data/ics10_grandpa_cw.wasm.gz") + suite.Require().NoError(err) + msg := types.NewMsgStoreCode(signer, code) + + res, err := GetSimApp(suite.chainA).WasmClientKeeper.StoreCode(suite.chainA.GetContext(), msg) + suite.Require().NoError(err) + + req = &types.QueryCodeRequest{Checksum: hex.EncodeToString(res.Checksum)} + }, + true, + }, + { + "fails with empty request", + func() { + req = &types.QueryCodeRequest{} + }, + false, + }, + { + "fails with non-existent checksum", + func() { + req = &types.QueryCodeRequest{Checksum: "test"} + }, + false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + tc.malleate() + + res, err := GetSimApp(suite.chainA).WasmClientKeeper.Code(suite.chainA.GetContext(), req) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().NotEmpty(res.Data) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryChecksums() { + var expChecksums []string + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "success with no checksums", + func() { + expChecksums = []string{} + }, + true, + }, + { + "success with one checksum", + func() { + signer := authtypes.NewModuleAddress(govtypes.ModuleName).String() + code, err := os.ReadFile("../test_data/ics10_grandpa_cw.wasm.gz") + suite.Require().NoError(err) + msg := types.NewMsgStoreCode(signer, code) + + res, err := GetSimApp(suite.chainA).WasmClientKeeper.StoreCode(suite.chainA.GetContext(), msg) + suite.Require().NoError(err) + + expChecksums = append(expChecksums, hex.EncodeToString(res.Checksum)) + }, + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + tc.malleate() + + req := &types.QueryChecksumsRequest{} + res, err := GetSimApp(suite.chainA).WasmClientKeeper.Checksums(suite.chainA.GetContext(), req) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(len(expChecksums), len(res.Checksums)) + suite.Require().ElementsMatch(expChecksums, res.Checksums) + } else { + suite.Require().Error(err) + } + }) + } +} diff --git a/modules/light-clients/08-wasm/keeper/keeper.go b/modules/light-clients/08-wasm/keeper/keeper.go new file mode 100644 index 00000000000..3fb6bec81a6 --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/keeper.go @@ -0,0 +1,194 @@ +package keeper + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "strings" + + wasmvm "github.com/CosmWasm/wasmvm" + + storetypes "cosmossdk.io/core/store" + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" +) + +// Keeper defines the 08-wasm keeper +type Keeper struct { + // implements gRPC QueryServer interface + types.QueryServer + + cdc codec.BinaryCodec + wasmVM ibcwasm.WasmEngine + + clientKeeper types.ClientKeeper + + authority string +} + +// NewKeeperWithVM creates a new Keeper instance with the provided Wasm VM. +// This constructor function is meant to be used when the chain uses x/wasm +// and the same Wasm VM instance should be shared with it. +func NewKeeperWithVM( + cdc codec.BinaryCodec, + storeService storetypes.KVStoreService, + clientKeeper types.ClientKeeper, + authority string, + vm ibcwasm.WasmEngine, +) Keeper { + if clientKeeper == nil { + panic(errors.New("client keeper must be not nil")) + } + + if vm == nil { + panic(errors.New("wasm VM must be not nil")) + } + + if storeService == nil { + panic(errors.New("store service must be not nil")) + } + + if strings.TrimSpace(authority) == "" { + panic(errors.New("authority must be non-empty")) + } + + ibcwasm.SetVM(vm) + ibcwasm.SetupWasmStoreService(storeService) + + return Keeper{ + cdc: cdc, + wasmVM: vm, + clientKeeper: clientKeeper, + authority: authority, + } +} + +// NewKeeperWithConfig creates a new Keeper instance with the provided Wasm configuration. +// This constructor function is meant to be used when the chain does not use x/wasm +// and a Wasm VM needs to be instantiated using the provided parameters. +func NewKeeperWithConfig( + cdc codec.BinaryCodec, + storeService storetypes.KVStoreService, + clientKeeper types.ClientKeeper, + authority string, + wasmConfig types.WasmConfig, +) Keeper { + vm, err := wasmvm.NewVM(wasmConfig.DataDir, wasmConfig.SupportedCapabilities, types.ContractMemoryLimit, wasmConfig.ContractDebugMode, types.MemoryCacheSize) + if err != nil { + panic(fmt.Errorf("failed to instantiate new Wasm VM instance: %v", err)) + } + + return NewKeeperWithVM(cdc, storeService, clientKeeper, authority, vm) +} + +// GetAuthority returns the 08-wasm module's authority. +func (k Keeper) GetAuthority() string { + return k.authority +} + +func generateWasmChecksum(code []byte) []byte { + hash := sha256.Sum256(code) + return hash[:] +} + +func (k Keeper) storeWasmCode(ctx sdk.Context, code []byte) ([]byte, error) { + var err error + if types.IsGzip(code) { + ctx.GasMeter().ConsumeGas(types.VMGasRegister.UncompressCosts(len(code)), "Uncompress gzip bytecode") + code, err = types.Uncompress(code, types.MaxWasmByteSize()) + if err != nil { + return nil, errorsmod.Wrap(err, "failed to store contract") + } + } + + // Check to see if store already has checksum. + checksum := generateWasmChecksum(code) + if types.HasChecksum(ctx, checksum) { + return nil, types.ErrWasmCodeExists + } + + // run the code through the wasm light client validation process + if err := types.ValidateWasmCode(code); err != nil { + return nil, errorsmod.Wrap(err, "wasm bytecode validation failed") + } + + // create the code in the vm + ctx.GasMeter().ConsumeGas(types.VMGasRegister.CompileCosts(len(code)), "Compiling wasm bytecode") + vmChecksum, err := k.wasmVM.StoreCode(code) + if err != nil { + return nil, errorsmod.Wrap(err, "failed to store contract") + } + + // SANITY: We've checked our store, additional safety check to assert that the checksum returned by WasmVM equals checksum generated by us. + if !bytes.Equal(vmChecksum, checksum) { + return nil, errorsmod.Wrapf(types.ErrInvalidChecksum, "expected %s, got %s", hex.EncodeToString(checksum), hex.EncodeToString(vmChecksum)) + } + + // pin the code to the vm in-memory cache + if err := k.wasmVM.Pin(vmChecksum); err != nil { + return nil, errorsmod.Wrapf(err, "failed to pin contract with checksum (%s) to vm cache", hex.EncodeToString(vmChecksum)) + } + + // store the checksum + err = ibcwasm.Checksums.Set(ctx, checksum) + if err != nil { + return nil, errorsmod.Wrap(err, "failed to store checksum") + } + + return checksum, nil +} + +func (k Keeper) migrateContractCode(ctx sdk.Context, clientID string, newChecksum, migrateMsg []byte) error { + wasmClientState, err := k.GetWasmClientState(ctx, clientID) + if err != nil { + return errorsmod.Wrap(err, "failed to retrieve wasm client state") + } + oldChecksum := wasmClientState.Checksum + + clientStore := k.clientKeeper.ClientStore(ctx, clientID) + + err = wasmClientState.MigrateContract(ctx, k.cdc, clientStore, clientID, newChecksum, migrateMsg) + if err != nil { + return errorsmod.Wrap(err, "contract migration failed") + } + + // client state may be updated by the contract migration + wasmClientState, err = k.GetWasmClientState(ctx, clientID) + if err != nil { + // note that this also ensures that the updated client state is + // still a wasm client state + return errorsmod.Wrap(err, "failed to retrieve the updated wasm client state") + } + + // update the client state checksum before persisting it + wasmClientState.Checksum = newChecksum + + k.clientKeeper.SetClientState(ctx, clientID, wasmClientState) + + emitMigrateContractEvent(ctx, clientID, oldChecksum, newChecksum) + + return nil +} + +// GetWasmClientState returns the 08-wasm client state for the given client identifier. +func (k Keeper) GetWasmClientState(ctx sdk.Context, clientID string) (*types.ClientState, error) { + clientState, found := k.clientKeeper.GetClientState(ctx, clientID) + if !found { + return nil, errorsmod.Wrapf(clienttypes.ErrClientTypeNotFound, "clientID %s", clientID) + } + + wasmClientState, ok := clientState.(*types.ClientState) + if !ok { + return nil, errorsmod.Wrapf(clienttypes.ErrInvalidClient, "expected type %T, got %T", (*types.ClientState)(nil), wasmClientState) + } + + return wasmClientState, nil +} diff --git a/modules/light-clients/08-wasm/keeper/keeper_test.go b/modules/light-clients/08-wasm/keeper/keeper_test.go new file mode 100644 index 00000000000..7beea146c96 --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/keeper_test.go @@ -0,0 +1,233 @@ +package keeper_test + +import ( + "encoding/json" + "errors" + "testing" + + wasmvm "github.com/CosmWasm/wasmvm" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + dbm "github.com/cosmos/cosmos-db" + testifysuite "github.com/stretchr/testify/suite" + + "cosmossdk.io/log" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/runtime" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing/simapp" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + "github.com/cosmos/ibc-go/v8/modules/core/exported" + ibctesting "github.com/cosmos/ibc-go/v8/testing" +) + +const ( + defaultWasmClientID = "08-wasm-0" +) + +type KeeperTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + // mockVM is a mock wasm VM that implements the WasmEngine interface + mockVM *wasmtesting.MockWasmEngine + chainA *ibctesting.TestChain +} + +func init() { + ibctesting.DefaultTestingAppInit = setupTestingApp +} + +// setupTestingApp provides the duplicated simapp which is specific to the 08-wasm module on chain creation. +func setupTestingApp() (ibctesting.TestingApp, map[string]json.RawMessage) { + db := dbm.NewMemDB() + app := simapp.NewSimApp(log.NewNopLogger(), db, nil, true, simtestutil.EmptyAppOptions{}, nil) + return app, app.DefaultGenesis() +} + +// GetSimApp returns the duplicated SimApp from within the 08-wasm directory. +// This must be used instead of chain.GetSimApp() for tests within this directory. +func GetSimApp(chain *ibctesting.TestChain) *simapp.SimApp { + app, ok := chain.App.(*simapp.SimApp) + if !ok { + panic(errors.New("chain is not a simapp.SimApp")) + } + return app +} + +func (suite *KeeperTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 1) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + + queryHelper := baseapp.NewQueryServerTestHelper(suite.chainA.GetContext(), GetSimApp(suite.chainA).InterfaceRegistry()) + types.RegisterQueryServer(queryHelper, GetSimApp(suite.chainA).WasmClientKeeper) +} + +// SetupWasmWithMockVM sets up mock cometbft chain with a mock vm. +func (suite *KeeperTestSuite) SetupWasmWithMockVM() { + ibctesting.DefaultTestingAppInit = suite.setupWasmWithMockVM + + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 1) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + + _ = storeWasmCode(suite, wasmtesting.Code) +} + +func (suite *KeeperTestSuite) setupWasmWithMockVM() (ibctesting.TestingApp, map[string]json.RawMessage) { + suite.mockVM = wasmtesting.NewMockWasmEngine() + + suite.mockVM.InstantiateFn = func(checksum wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + var payload types.InstantiateMessage + err := json.Unmarshal(initMsg, &payload) + suite.Require().NoError(err) + + store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), payload.ClientState)) + store.Set(host.ConsensusStateKey(payload.ClientState.LatestHeight), clienttypes.MustMarshalConsensusState(suite.chainA.App.AppCodec(), payload.ConsensusState)) + + resp, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.Response{Data: resp}, 0, nil + } + + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(checksum wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) ([]byte, uint64, error) { + resp, err := json.Marshal(types.StatusResult{Status: exported.Active.String()}) + suite.Require().NoError(err) + return resp, wasmtesting.DefaultGasUsed, nil + }) + + db := dbm.NewMemDB() + app := simapp.NewSimApp(log.NewNopLogger(), db, nil, true, simtestutil.EmptyAppOptions{}, suite.mockVM) + + // reset DefaultTestingAppInit to its original value + ibctesting.DefaultTestingAppInit = setupTestingApp + return app, app.DefaultGenesis() +} + +// storeWasmCode stores the wasm code on chain and returns the checksum. +func storeWasmCode(suite *KeeperTestSuite, wasmCode []byte) []byte { + ctx := suite.chainA.GetContext().WithBlockGasMeter(storetypes.NewInfiniteGasMeter()) + + msg := types.NewMsgStoreCode(authtypes.NewModuleAddress(govtypes.ModuleName).String(), wasmCode) + response, err := GetSimApp(suite.chainA).WasmClientKeeper.StoreCode(ctx, msg) + suite.Require().NoError(err) + suite.Require().NotNil(response.Checksum) + return response.Checksum +} + +func (suite *KeeperTestSuite) SetupSnapshotterWithMockVM() *simapp.SimApp { + suite.mockVM = wasmtesting.NewMockWasmEngine() + + return simapp.SetupWithSnapshotter(suite.T(), suite.mockVM) +} + +func TestKeeperTestSuite(t *testing.T) { + testifysuite.Run(t, new(KeeperTestSuite)) +} + +func (suite *KeeperTestSuite) TestNewKeeper() { + testCases := []struct { + name string + instantiateFn func() + expPass bool + expError error + }{ + { + "success", + func() { + keeper.NewKeeperWithVM( + GetSimApp(suite.chainA).AppCodec(), + runtime.NewKVStoreService(GetSimApp(suite.chainA).GetKey(types.StoreKey)), + GetSimApp(suite.chainA).IBCKeeper.ClientKeeper, + GetSimApp(suite.chainA).WasmClientKeeper.GetAuthority(), + ibcwasm.GetVM(), + ) + }, + true, + nil, + }, + { + "failure: empty authority", + func() { + keeper.NewKeeperWithVM( + GetSimApp(suite.chainA).AppCodec(), + runtime.NewKVStoreService(GetSimApp(suite.chainA).GetKey(types.StoreKey)), + GetSimApp(suite.chainA).IBCKeeper.ClientKeeper, + "", // authority + ibcwasm.GetVM(), + ) + }, + false, + errors.New("authority must be non-empty"), + }, + { + "failure: nil client keeper", + func() { + keeper.NewKeeperWithVM( + GetSimApp(suite.chainA).AppCodec(), + runtime.NewKVStoreService(GetSimApp(suite.chainA).GetKey(types.StoreKey)), + nil, // client keeper, + GetSimApp(suite.chainA).WasmClientKeeper.GetAuthority(), + ibcwasm.GetVM(), + ) + }, + false, + errors.New("client keeper must be not nil"), + }, + { + "failure: nil wasm VM", + func() { + keeper.NewKeeperWithVM( + GetSimApp(suite.chainA).AppCodec(), + runtime.NewKVStoreService(GetSimApp(suite.chainA).GetKey(types.StoreKey)), + GetSimApp(suite.chainA).IBCKeeper.ClientKeeper, + GetSimApp(suite.chainA).WasmClientKeeper.GetAuthority(), + nil, + ) + }, + false, + errors.New("wasm VM must be not nil"), + }, + { + "failure: nil store service", + func() { + keeper.NewKeeperWithVM( + GetSimApp(suite.chainA).AppCodec(), + nil, + GetSimApp(suite.chainA).IBCKeeper.ClientKeeper, + GetSimApp(suite.chainA).WasmClientKeeper.GetAuthority(), + ibcwasm.GetVM(), + ) + }, + false, + errors.New("store service must be not nil"), + }, + } + + for _, tc := range testCases { + tc := tc + suite.SetupTest() + + suite.Run(tc.name, func() { + if tc.expPass { + suite.Require().NotPanics( + tc.instantiateFn, + ) + } else { + suite.Require().PanicsWithError(tc.expError.Error(), func() { + tc.instantiateFn() + }) + } + }) + } +} diff --git a/modules/light-clients/08-wasm/keeper/msg_server.go b/modules/light-clients/08-wasm/keeper/msg_server.go new file mode 100644 index 00000000000..d63d5ded4bb --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/msg_server.go @@ -0,0 +1,76 @@ +package keeper + +import ( + "context" + "encoding/hex" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + ibcerrors "github.com/cosmos/ibc-go/v8/modules/core/errors" +) + +var _ types.MsgServer = (*Keeper)(nil) + +// StoreCode defines a rpc handler method for MsgStoreCode +func (k Keeper) StoreCode(goCtx context.Context, msg *types.MsgStoreCode) (*types.MsgStoreCodeResponse, error) { + if k.GetAuthority() != msg.Signer { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "expected %s, got %s", k.GetAuthority(), msg.Signer) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + checksum, err := k.storeWasmCode(ctx, msg.WasmByteCode) + if err != nil { + return nil, errorsmod.Wrap(err, "failed to store wasm bytecode") + } + + emitStoreWasmCodeEvent(ctx, checksum) + + return &types.MsgStoreCodeResponse{ + Checksum: checksum, + }, nil +} + +// RemoveChecksum defines a rpc handler method for MsgRemoveChecksum +func (k Keeper) RemoveChecksum(goCtx context.Context, msg *types.MsgRemoveChecksum) (*types.MsgRemoveChecksumResponse, error) { + if k.GetAuthority() != msg.Signer { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "expected %s, got %s", k.GetAuthority(), msg.Signer) + } + + if !types.HasChecksum(goCtx, msg.Checksum) { + return nil, types.ErrWasmChecksumNotFound + } + + err := ibcwasm.Checksums.Remove(goCtx, msg.Checksum) + if err != nil { + return nil, errorsmod.Wrap(err, "failed to remove checksum") + } + + // unpin the code from the vm in-memory cache + if err := k.wasmVM.Unpin(msg.Checksum); err != nil { + return nil, errorsmod.Wrapf(err, "failed to unpin contract with checksum (%s) from vm cache", hex.EncodeToString(msg.Checksum)) + } + + return &types.MsgRemoveChecksumResponse{}, nil +} + +// MigrateContract defines a rpc handler method for MsgMigrateContract +func (k Keeper) MigrateContract(goCtx context.Context, msg *types.MsgMigrateContract) (*types.MsgMigrateContractResponse, error) { + if k.GetAuthority() != msg.Signer { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "expected %s, got %s", k.GetAuthority(), msg.Signer) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + err := k.migrateContractCode(ctx, msg.ClientId, msg.Checksum, msg.Msg) + if err != nil { + return nil, errorsmod.Wrap(err, "failed to migrate contract") + } + + // event emission is handled in migrateContractCode + + return &types.MsgMigrateContractResponse{}, nil +} diff --git a/modules/light-clients/08-wasm/keeper/msg_server_test.go b/modules/light-clients/08-wasm/keeper/msg_server_test.go new file mode 100644 index 00000000000..cdc4f9bfeda --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/msg_server_test.go @@ -0,0 +1,395 @@ +package keeper_test + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "os" + + wasmvm "github.com/CosmWasm/wasmvm" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v8/modules/core/errors" + localhost "github.com/cosmos/ibc-go/v8/modules/light-clients/09-localhost" + ibctesting "github.com/cosmos/ibc-go/v8/testing" +) + +func (suite *KeeperTestSuite) TestMsgStoreCode() { + var ( + msg *types.MsgStoreCode + signer string + data []byte + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + msg = types.NewMsgStoreCode(signer, data) + }, + nil, + }, + { + "fails with duplicate wasm code", + func() { + msg = types.NewMsgStoreCode(signer, data) + + _, err := GetSimApp(suite.chainA).WasmClientKeeper.StoreCode(suite.chainA.GetContext(), msg) + suite.Require().NoError(err) + }, + types.ErrWasmCodeExists, + }, + { + "fails with invalid wasm code", + func() { + msg = types.NewMsgStoreCode(signer, []byte{}) + }, + types.ErrWasmEmptyCode, + }, + { + "fails with unauthorized signer", + func() { + signer = suite.chainA.SenderAccount.GetAddress().String() + msg = types.NewMsgStoreCode(signer, data) + }, + ibcerrors.ErrUnauthorized, + }, + { + "failure: checksum could not be pinned", + func() { + msg = types.NewMsgStoreCode(signer, data) + + suite.mockVM.PinFn = func(_ wasmvm.Checksum) error { + return wasmtesting.ErrMockVM + } + }, + wasmtesting.ErrMockVM, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + signer = authtypes.NewModuleAddress(govtypes.ModuleName).String() + data, _ = os.ReadFile("../test_data/ics10_grandpa_cw.wasm.gz") + + tc.malleate() + + ctx := suite.chainA.GetContext() + res, err := GetSimApp(suite.chainA).WasmClientKeeper.StoreCode(ctx, msg) + events := ctx.EventManager().Events() + + if tc.expError == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().NotEmpty(res.Checksum) + + // Verify events + expectedEvents := sdk.Events{ + sdk.NewEvent( + "store_wasm_code", + sdk.NewAttribute(types.AttributeKeyWasmChecksum, hex.EncodeToString(res.Checksum)), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + } + + for _, evt := range expectedEvents { + suite.Require().Contains(events, evt) + } + } else { + suite.Require().ErrorIs(err, tc.expError) + suite.Require().Nil(res) + suite.Require().Empty(events) + } + }) + } +} + +func (suite *KeeperTestSuite) TestMsgMigrateContract() { + oldChecksum := sha256.Sum256(wasmtesting.Code) + + newByteCode := []byte("MockByteCode-TestMsgMigrateContract") + + govAcc := authtypes.NewModuleAddress(govtypes.ModuleName).String() + + var ( + newChecksum []byte + msg *types.MsgMigrateContract + expClientState *types.ClientState + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success: no update to client state", + func() { + msg = types.NewMsgMigrateContract(govAcc, defaultWasmClientID, newChecksum, []byte("{}")) + + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.Response{Data: data}, wasmtesting.DefaultGasUsed, nil + } + }, + nil, + }, + { + "success: update client state", + func() { + msg = types.NewMsgMigrateContract(govAcc, defaultWasmClientID, newChecksum, []byte("{}")) + + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + // the checksum written in the client state will later be overwritten by the message server. + expClientState = types.NewClientState([]byte{1}, []byte("invalid checksum"), clienttypes.NewHeight(2000, 2)) + store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), expClientState)) + + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.Response{Data: data}, wasmtesting.DefaultGasUsed, nil + } + }, + nil, + }, + { + "failure: same checksum", + func() { + msg = types.NewMsgMigrateContract(govAcc, defaultWasmClientID, oldChecksum[:], []byte("{}")) + + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + panic("unreachable") + } + }, + types.ErrWasmCodeExists, + }, + { + "failure: unauthorized signer", + func() { + msg = types.NewMsgMigrateContract(suite.chainA.SenderAccount.GetAddress().String(), defaultWasmClientID, newChecksum, []byte("{}")) + }, + ibcerrors.ErrUnauthorized, + }, + { + "failure: invalid wasm checksum", + func() { + msg = types.NewMsgMigrateContract(govAcc, defaultWasmClientID, []byte(ibctesting.InvalidID), []byte("{}")) + }, + types.ErrWasmChecksumNotFound, + }, + { + "failure: invalid client id", + func() { + msg = types.NewMsgMigrateContract(govAcc, ibctesting.InvalidID, newChecksum, []byte("{}")) + }, + clienttypes.ErrClientTypeNotFound, + }, + { + "failure: contract returns error", + func() { + msg = types.NewMsgMigrateContract(govAcc, defaultWasmClientID, newChecksum, []byte("{}")) + + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + return nil, wasmtesting.DefaultGasUsed, wasmtesting.ErrMockContract + } + }, + types.ErrWasmContractCallFailed, + }, + { + "failure: incorrect state update", + func() { + msg = types.NewMsgMigrateContract(govAcc, defaultWasmClientID, newChecksum, []byte("{}")) + + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + // the checksum written in here will be overwritten + newClientState := localhost.NewClientState(clienttypes.NewHeight(1, 1)) + + store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), newClientState)) + + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.Response{Data: data}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmInvalidContractModification, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + newChecksum = storeWasmCode(suite, newByteCode) + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + + // this is the old client state + expClientState = endpoint.GetClientState().(*types.ClientState) + + tc.malleate() + + ctx := suite.chainA.GetContext() + res, err := GetSimApp(suite.chainA).WasmClientKeeper.MigrateContract(ctx, msg) + events := ctx.EventManager().Events().ToABCIEvents() + + if tc.expError == nil { + expClientState.Checksum = newChecksum + + suite.Require().NoError(err) + suite.Require().NotNil(res) + + // updated client state + clientState, ok := endpoint.GetClientState().(*types.ClientState) + suite.Require().True(ok) + + suite.Require().Equal(expClientState, clientState) + + // Verify events + expectedEvents := sdk.Events{ + sdk.NewEvent( + "migrate_contract", + sdk.NewAttribute(types.AttributeKeyClientID, defaultWasmClientID), + sdk.NewAttribute(types.AttributeKeyWasmChecksum, hex.EncodeToString(oldChecksum[:])), + sdk.NewAttribute(types.AttributeKeyNewChecksum, hex.EncodeToString(newChecksum)), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }.ToABCIEvents() + + for _, evt := range expectedEvents { + suite.Require().Contains(events, evt) + } + } else { + suite.Require().ErrorIs(err, tc.expError) + suite.Require().Nil(res) + } + }) + } +} + +func (suite *KeeperTestSuite) TestMsgRemoveChecksum() { + checksum := sha256.Sum256(wasmtesting.Code) + + govAcc := authtypes.NewModuleAddress(govtypes.ModuleName).String() + + var ( + msg *types.MsgRemoveChecksum + expChecksums []types.Checksum + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + msg = types.NewMsgRemoveChecksum(govAcc, checksum[:]) + + expChecksums = []types.Checksum{} + }, + nil, + }, + { + "success: many checksums", + func() { + msg = types.NewMsgRemoveChecksum(govAcc, checksum[:]) + + expChecksums = []types.Checksum{} + + for i := 0; i < 20; i++ { + checksum := sha256.Sum256([]byte{byte(i)}) + err := ibcwasm.Checksums.Set(suite.chainA.GetContext(), checksum[:]) + suite.Require().NoError(err) + + expChecksums = append(expChecksums, checksum[:]) + } + }, + nil, + }, + { + "failure: checksum is missing", + func() { + msg = types.NewMsgRemoveChecksum(govAcc, []byte{1}) + }, + types.ErrWasmChecksumNotFound, + }, + { + "failure: unauthorized signer", + func() { + msg = types.NewMsgRemoveChecksum(suite.chainA.SenderAccount.GetAddress().String(), checksum[:]) + }, + ibcerrors.ErrUnauthorized, + }, + { + "failure: code has could not be unpinned", + func() { + msg = types.NewMsgRemoveChecksum(govAcc, checksum[:]) + + suite.mockVM.UnpinFn = func(_ wasmvm.Checksum) error { + return wasmtesting.ErrMockVM + } + }, + wasmtesting.ErrMockVM, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + + tc.malleate() + + ctx := suite.chainA.GetContext() + res, err := GetSimApp(suite.chainA).WasmClientKeeper.RemoveChecksum(ctx, msg) + events := ctx.EventManager().Events().ToABCIEvents() + + if tc.expError == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + + checksums, err := types.GetAllChecksums(suite.chainA.GetContext()) + suite.Require().NoError(err) + + // Check equality of checksums up to order + suite.Require().ElementsMatch(expChecksums, checksums) + + // Verify events + suite.Require().Len(events, 0) + } else { + suite.Require().ErrorIs(err, tc.expError) + suite.Require().Nil(res) + } + }) + } +} diff --git a/modules/light-clients/08-wasm/keeper/snapshotter.go b/modules/light-clients/08-wasm/keeper/snapshotter.go new file mode 100644 index 00000000000..91540d27aad --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/snapshotter.go @@ -0,0 +1,148 @@ +package keeper + +import ( + "encoding/hex" + "io" + + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + + errorsmod "cosmossdk.io/errors" + snapshot "cosmossdk.io/store/snapshots/types" + storetypes "cosmossdk.io/store/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" +) + +var _ snapshot.ExtensionSnapshotter = &WasmSnapshotter{} + +// SnapshotFormat defines the default snapshot extension encoding format. +// SnapshotFormat 1 is gzipped wasm byte code for each item payload. No protobuf envelope, no metadata. +const SnapshotFormat = 1 + +// WasmSnapshotter implements the snapshot.ExtensionSnapshotter interface and is used to +// import and export state maintained within the wasmvm cache. +// NOTE: The following ExtensionSnapshotter has been adapted from CosmWasm's x/wasm: +// https://github.com/CosmWasm/wasmd/blob/v0.43.0/x/wasm/keeper/snapshotter.go +type WasmSnapshotter struct { + cms storetypes.MultiStore + keeper *Keeper +} + +// NewWasmSnapshotter creates and returns a new snapshot.ExtensionSnapshotter implementation for the 08-wasm module. +func NewWasmSnapshotter(cms storetypes.MultiStore, keeper *Keeper) snapshot.ExtensionSnapshotter { + return &WasmSnapshotter{ + cms: cms, + keeper: keeper, + } +} + +// SnapshotName implements the snapshot.ExtensionSnapshotter interface. +// A unique name should be provided such that the implementation can be identified by the manager. +func (*WasmSnapshotter) SnapshotName() string { + return types.ModuleName +} + +// SnapshotFormat implements the snapshot.ExtensionSnapshotter interface. +// This is the default format used for encoding payloads when taking a snapshot. +func (*WasmSnapshotter) SnapshotFormat() uint32 { + return SnapshotFormat +} + +// SupportedFormats implements the snapshot.ExtensionSnapshotter interface. +// This defines a list of supported formats the snapshotter extension can restore from. +func (*WasmSnapshotter) SupportedFormats() []uint32 { + return []uint32{SnapshotFormat} +} + +// SnapshotExtension implements the snapshot.ExntensionSnapshotter interface. +// SnapshotExtension is used to write data payloads into the underlying protobuf stream from the 08-wasm module. +func (ws *WasmSnapshotter) SnapshotExtension(height uint64, payloadWriter snapshot.ExtensionPayloadWriter) error { + cacheMS, err := ws.cms.CacheMultiStoreWithVersion(int64(height)) + if err != nil { + return err + } + + ctx := sdk.NewContext(cacheMS, tmproto.Header{}, false, nil) + + checksums, err := types.GetAllChecksums(ctx) + if err != nil { + return err + } + + for _, checksum := range checksums { + wasmCode, err := ws.keeper.wasmVM.GetCode(wasmvmtypes.Checksum(checksum)) + if err != nil { + return err + } + + compressedWasm, err := types.GzipIt(wasmCode) + if err != nil { + return err + } + + if err = payloadWriter(compressedWasm); err != nil { + return err + } + } + + return nil +} + +// RestoreExtension implements the snapshot.ExtensionSnapshotter interface. +// RestoreExtension is used to read data from an existing extension state snapshot into the 08-wasm module. +// The payload reader returns io.EOF when it has reached the end of the extension state snapshot. +func (ws *WasmSnapshotter) RestoreExtension(height uint64, format uint32, payloadReader snapshot.ExtensionPayloadReader) error { + if format == ws.SnapshotFormat() { + return ws.processAllItems(height, payloadReader, restoreV1) + } + + return errorsmod.Wrapf(snapshot.ErrUnknownFormat, "expected %d, got %d", ws.SnapshotFormat(), format) +} + +func restoreV1(ctx sdk.Context, k *Keeper, compressedCode []byte) error { + if !types.IsGzip(compressedCode) { + return errorsmod.Wrap(types.ErrInvalidData, "expected wasm code is not gzip format") + } + + wasmCode, err := types.Uncompress(compressedCode, types.MaxWasmByteSize()) + if err != nil { + return errorsmod.Wrap(err, "failed to uncompress wasm code") + } + + checksum, err := k.wasmVM.StoreCode(wasmCode) + if err != nil { + return errorsmod.Wrap(err, "failed to store wasm code") + } + + if err := k.wasmVM.Pin(checksum); err != nil { + return errorsmod.Wrapf(err, "failed to pin checksum: %s to in-memory cache", hex.EncodeToString(checksum)) + } + + return nil +} + +func (ws *WasmSnapshotter) processAllItems( + height uint64, + payloadReader snapshot.ExtensionPayloadReader, + cb func(sdk.Context, *Keeper, []byte) error, +) error { + ctx := sdk.NewContext(ws.cms, tmproto.Header{Height: int64(height)}, false, nil) + for { + payload, err := payloadReader() + if err == io.EOF { + break + } else if err != nil { + return err + } + + if err := cb(ctx, ws.keeper, payload); err != nil { + return errorsmod.Wrap(err, "failure processing snapshot item") + } + } + + return nil +} diff --git a/modules/light-clients/08-wasm/keeper/snapshotter_test.go b/modules/light-clients/08-wasm/keeper/snapshotter_test.go new file mode 100644 index 00000000000..ccfb63e818e --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/snapshotter_test.go @@ -0,0 +1,119 @@ +package keeper_test + +import ( + "encoding/hex" + "time" + + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing/simapp" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" +) + +func (suite *KeeperTestSuite) TestSnapshotter() { + gzippedContract, err := types.GzipIt([]byte("gzipped-contract")) + suite.Require().NoError(err) + + testCases := []struct { + name string + contracts [][]byte + }{ + { + name: "single contract", + contracts: [][]byte{wasmtesting.Code}, + }, + { + name: "multiple contracts", + contracts: [][]byte{wasmtesting.Code, gzippedContract}, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + t := suite.T() + wasmClientApp := suite.SetupSnapshotterWithMockVM() + + ctx := wasmClientApp.NewUncachedContext(false, tmproto.Header{ + ChainID: "foo", + Height: wasmClientApp.LastBlockHeight() + 1, + Time: time.Now(), + }) + + var srcChecksumCodes []byte + var checksums [][]byte + // store contract on chain + for _, contract := range tc.contracts { + signer := authtypes.NewModuleAddress(govtypes.ModuleName).String() + msg := types.NewMsgStoreCode(signer, contract) + + res, err := wasmClientApp.WasmClientKeeper.StoreCode(ctx, msg) + suite.Require().NoError(err) + + checksums = append(checksums, res.Checksum) + srcChecksumCodes = append(srcChecksumCodes, res.Checksum...) + + suite.Require().NoError(err) + } + + // create snapshot + res, err := wasmClientApp.Commit() + suite.Require().NoError(err) + suite.Require().NotNil(res) + + snapshotHeight := uint64(wasmClientApp.LastBlockHeight()) + snapshot, err := wasmClientApp.SnapshotManager().Create(snapshotHeight) + suite.Require().NoError(err) + suite.Require().NotNil(snapshot) + + // setup dest app with snapshot imported + destWasmClientApp := simapp.SetupWithEmptyStore(t, suite.mockVM) + destCtx := destWasmClientApp.NewUncachedContext(false, tmproto.Header{ + ChainID: "bar", + Height: destWasmClientApp.LastBlockHeight() + 1, + Time: time.Now(), + }) + + resp, err := destWasmClientApp.WasmClientKeeper.Checksums(destCtx, &types.QueryChecksumsRequest{}) + suite.Require().NoError(err) + suite.Require().Empty(resp.Checksums) + + suite.Require().NoError(destWasmClientApp.SnapshotManager().Restore(*snapshot)) + + for i := uint32(0); i < snapshot.Chunks; i++ { + chunkBz, err := wasmClientApp.SnapshotManager().LoadChunk(snapshot.Height, snapshot.Format, i) + suite.Require().NoError(err) + + end, err := destWasmClientApp.SnapshotManager().RestoreChunk(chunkBz) + suite.Require().NoError(err) + + if end { + break + } + } + + var allDestAppChecksumsInWasmVMStore []byte + // check wasm contracts are imported + ctx = destWasmClientApp.NewUncachedContext(false, tmproto.Header{ + ChainID: "foo", + Height: destWasmClientApp.LastBlockHeight() + 1, + Time: time.Now(), + }) + + for _, checksum := range checksums { + resp, err := destWasmClientApp.WasmClientKeeper.Code(ctx, &types.QueryCodeRequest{Checksum: hex.EncodeToString(checksum)}) + suite.Require().NoError(err) + + allDestAppChecksumsInWasmVMStore = append(allDestAppChecksumsInWasmVMStore, keeper.GenerateWasmChecksum(resp.Data)...) + } + + suite.Require().Equal(srcChecksumCodes, allDestAppChecksumsInWasmVMStore) + }) + } +} diff --git a/modules/light-clients/08-wasm/module.go b/modules/light-clients/08-wasm/module.go new file mode 100644 index 00000000000..b085d0f894c --- /dev/null +++ b/modules/light-clients/08-wasm/module.go @@ -0,0 +1,139 @@ +package wasm + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + + "cosmossdk.io/core/appmodule" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/gov/simulation" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/client/cli" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" +) + +var ( + _ module.AppModule = (*AppModule)(nil) + _ module.AppModuleBasic = (*AppModule)(nil) + _ module.HasProposalMsgs = (*AppModule)(nil) + _ module.HasGenesis = (*AppModule)(nil) + _ module.HasName = (*AppModule)(nil) + _ module.HasConsensusVersion = (*AppModule)(nil) + _ module.HasServices = (*AppModule)(nil) + _ appmodule.AppModule = (*AppModule)(nil) +) + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (AppModule) IsOnePerModuleType() {} + +// IsAppModule implements the appmodule.AppModule interface. +func (AppModule) IsAppModule() {} + +// AppModuleBasic defines the basic application module used by the Wasm light client. +// Only the RegisterInterfaces function needs to be implemented. All other function perform +// a no-op. +type AppModuleBasic struct{} + +// Name returns the tendermint module name. +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +// RegisterLegacyAminoCodec performs a no-op. The Wasm client does not support amino. +func (AppModuleBasic) RegisterLegacyAminoCodec(*codec.LegacyAmino) {} + +// RegisterInterfaces registers module concrete types into protobuf Any. This allows core IBC +// to unmarshal Wasm light client types. +func (AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) { + types.RegisterInterfaces(registry) +} + +// DefaultGenesis returns an empty state, i.e. no contracts +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(&types.GenesisState{ + Contracts: []types.Contract{}, + }) +} + +// ValidateGenesis performs a no-op. +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, _ client.TxEncodingConfig, bz json.RawMessage) error { + var gs types.GenesisState + if err := cdc.UnmarshalJSON(bz, &gs); err != nil { + return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) + } + + return gs.Validate() +} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for Wasm client module. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { + err := types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)) + if err != nil { + panic(err) + } +} + +// GetTxCmd implements AppModuleBasic interface +func (AppModuleBasic) GetTxCmd() *cobra.Command { + return cli.NewTxCmd() +} + +// GetQueryCmd implements AppModuleBasic interface +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return cli.GetQueryCmd() +} + +// AppModule represents the AppModule for this module +type AppModule struct { + AppModuleBasic + keeper keeper.Keeper +} + +// NewAppModule creates a new 08-wasm module +func NewAppModule(k keeper.Keeper) AppModule { + return AppModule{ + keeper: k, + } +} + +// RegisterServices registers module services. +func (am AppModule) RegisterServices(cfg module.Configurator) { + types.RegisterMsgServer(cfg.MsgServer(), am.keeper) + types.RegisterQueryServer(cfg.QueryServer(), am.keeper) +} + +// ConsensusVersion implements AppModule/ConsensusVersion. +func (AppModule) ConsensusVersion() uint64 { return 1 } + +// ProposalMsgs returns msgs used for governance proposals for simulations. +func (AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.WeightedProposalMsg { + return simulation.ProposalMsgs() +} + +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, bz json.RawMessage) { + var gs types.GenesisState + err := cdc.UnmarshalJSON(bz, &gs) + if err != nil { + panic(fmt.Errorf("failed to unmarshal %s genesis state: %s", am.Name(), err)) + } + err = am.keeper.InitGenesis(ctx, gs) + if err != nil { + panic(err) + } +} + +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { + gs := am.keeper.ExportGenesis(ctx) + return cdc.MustMarshalJSON(&gs) +} diff --git a/modules/light-clients/08-wasm/simulation/proposals.go b/modules/light-clients/08-wasm/simulation/proposals.go new file mode 100644 index 00000000000..2767d5fa52c --- /dev/null +++ b/modules/light-clients/08-wasm/simulation/proposals.go @@ -0,0 +1,40 @@ +package simulation + +import ( + "math/rand" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" +) + +// Simulation operation weights constants +const ( + DefaultWeightMsgStoreCode int = 100 + + OpWeightMsgStoreCode = "op_weight_msg_store_code" // #nosec +) + +// ProposalMsgs defines the module weighted proposals' contents +func ProposalMsgs() []simtypes.WeightedProposalMsg { + return []simtypes.WeightedProposalMsg{ + simulation.NewWeightedProposalMsg( + OpWeightMsgStoreCode, + DefaultWeightMsgStoreCode, + SimulateMsgStoreCode, + ), + } +} + +// SimulateMsgStoreCode returns a random MsgStoreCode for the 08-wasm module +func SimulateMsgStoreCode(r *rand.Rand, _ sdk.Context, _ []simtypes.Account) sdk.Msg { + var signer sdk.AccAddress = address.Module("gov") + + return &types.MsgStoreCode{ + Signer: signer.String(), + WasmByteCode: []byte{0x01}, + } +} diff --git a/modules/light-clients/08-wasm/simulation/proposals_test.go b/modules/light-clients/08-wasm/simulation/proposals_test.go new file mode 100644 index 00000000000..cc1f6dd9925 --- /dev/null +++ b/modules/light-clients/08-wasm/simulation/proposals_test.go @@ -0,0 +1,41 @@ +package simulation_test + +import ( + "math/rand" + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/simulation" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" +) + +func TestProposalMsgs(t *testing.T) { + // initialize parameters + s := rand.NewSource(1) + r := rand.New(s) + + ctx := sdk.NewContext(nil, cmtproto.Header{}, true, nil) + accounts := simtypes.RandomAccounts(r, 3) + + // execute ProposalMsgs function + weightedProposalMsgs := simulation.ProposalMsgs() + require.Equal(t, 1, len(weightedProposalMsgs)) + w0 := weightedProposalMsgs[0] + + require.Equal(t, simulation.OpWeightMsgStoreCode, w0.AppParamsKey()) + require.Equal(t, simulation.DefaultWeightMsgStoreCode, w0.DefaultWeight()) + + msg := w0.MsgSimulatorFn()(r, ctx, accounts) + msgStoreCode, ok := msg.(*types.MsgStoreCode) + require.True(t, ok) + + require.Equal(t, sdk.AccAddress(address.Module("gov")).String(), msgStoreCode.Signer) + require.Equal(t, msgStoreCode.WasmByteCode, []byte{0x01}) +} diff --git a/modules/light-clients/08-wasm/test_data/ics07_tendermint_cw.wasm.gz b/modules/light-clients/08-wasm/test_data/ics07_tendermint_cw.wasm.gz new file mode 100755 index 00000000000..d642c9c1ea5 Binary files /dev/null and b/modules/light-clients/08-wasm/test_data/ics07_tendermint_cw.wasm.gz differ diff --git a/modules/light-clients/08-wasm/test_data/ics10_grandpa_cw.wasm.gz b/modules/light-clients/08-wasm/test_data/ics10_grandpa_cw.wasm.gz new file mode 100755 index 00000000000..e14afdc5dd1 Binary files /dev/null and b/modules/light-clients/08-wasm/test_data/ics10_grandpa_cw.wasm.gz differ diff --git a/modules/light-clients/08-wasm/testing/mock_engine.go b/modules/light-clients/08-wasm/testing/mock_engine.go new file mode 100644 index 00000000000..202bb901ff0 --- /dev/null +++ b/modules/light-clients/08-wasm/testing/mock_engine.go @@ -0,0 +1,270 @@ +package testing + +import ( + "crypto/sha256" + "encoding/binary" + "encoding/json" + "errors" + "fmt" + "reflect" + + wasmvm "github.com/CosmWasm/wasmvm" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" +) + +const DefaultGasUsed = uint64(1) + +var ( + _ ibcwasm.WasmEngine = (*MockWasmEngine)(nil) + + // queryTypes contains all the possible query message types. + queryTypes = [...]any{types.StatusMsg{}, types.ExportMetadataMsg{}, types.TimestampAtHeightMsg{}, types.VerifyClientMessageMsg{}, types.CheckForMisbehaviourMsg{}} + + // sudoTypes contains all the possible sudo message types. + sudoTypes = [...]any{types.UpdateStateMsg{}, types.UpdateStateOnMisbehaviourMsg{}, types.VerifyUpgradeAndUpdateStateMsg{}, types.VerifyMembershipMsg{}, types.VerifyNonMembershipMsg{}, types.MigrateClientStoreMsg{}} +) + +type ( + // queryFn is a callback function that is invoked when a specific query message type is received. + queryFn func(checksum wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) ([]byte, uint64, error) + + // sudoFn is a callback function that is invoked when a specific sudo message type is received. + sudoFn func(checksum wasmvm.Checksum, env wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) +) + +func NewMockWasmEngine() *MockWasmEngine { + m := &MockWasmEngine{ + queryCallbacks: map[string]queryFn{}, + sudoCallbacks: map[string]sudoFn{}, + storedContracts: map[uint32][]byte{}, + } + + for _, msgType := range queryTypes { + typeName := reflect.TypeOf(msgType).Name() + m.queryCallbacks[typeName] = func(checksum wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) ([]byte, uint64, error) { + panic(fmt.Errorf("no callback specified for type %s", typeName)) + } + } + + for _, msgType := range sudoTypes { + typeName := reflect.TypeOf(msgType).Name() + m.sudoCallbacks[typeName] = func(checksum wasmvm.Checksum, env wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + panic(fmt.Errorf("no callback specified for type %s", typeName)) + } + } + + // Set up default behavior for Store/Pin/Get + m.StoreCodeFn = func(code wasmvm.WasmCode) (wasmvm.Checksum, error) { + hash := sha256.Sum256(code) + checkSum := wasmvm.Checksum(hash[:]) + + m.storedContracts[binary.LittleEndian.Uint32(checkSum)] = code + return checkSum, nil + } + + m.PinFn = func(checksum wasmvm.Checksum) error { + return nil + } + + m.UnpinFn = func(checksum wasmvm.Checksum) error { + return nil + } + + m.GetCodeFn = func(checksum wasmvm.Checksum) (wasmvm.WasmCode, error) { + code, ok := m.storedContracts[binary.LittleEndian.Uint32(checksum)] + if !ok { + return nil, errors.New("code not found") + } + return code, nil + } + + return m +} + +// RegisterQueryCallback registers a callback for a specific message type. +func (m *MockWasmEngine) RegisterQueryCallback(queryMessage any, fn queryFn) { + typeName := reflect.TypeOf(queryMessage).Name() + if _, found := m.queryCallbacks[typeName]; !found { + panic(fmt.Errorf("unexpected argument of type %s passed", typeName)) + } + m.queryCallbacks[typeName] = fn +} + +// RegisterSudoCallback registers a callback for a specific sudo message type. +func (m *MockWasmEngine) RegisterSudoCallback(sudoMessage any, fn sudoFn) { + typeName := reflect.TypeOf(sudoMessage).Name() + if _, found := m.sudoCallbacks[typeName]; !found { + panic(fmt.Errorf("unexpected argument of type %s passed", typeName)) + } + m.sudoCallbacks[typeName] = fn +} + +// MockWasmEngine implements types.WasmEngine for testing purpose. One or multiple messages can be stubbed. +// Without a stub function a panic is thrown. +// ref: https://github.com/CosmWasm/wasmd/blob/v0.42.0/x/wasm/keeper/wasmtesting/mock_engine.go#L19 +type MockWasmEngine struct { + StoreCodeFn func(code wasmvm.WasmCode) (wasmvm.Checksum, error) + InstantiateFn func(checksum wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) + MigrateFn func(checksum wasmvm.Checksum, env wasmvmtypes.Env, migrateMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) + GetCodeFn func(checksum wasmvm.Checksum) (wasmvm.WasmCode, error) + PinFn func(checksum wasmvm.Checksum) error + UnpinFn func(checksum wasmvm.Checksum) error + + // queryCallbacks contains a mapping of queryMsg field type name to callback function. + queryCallbacks map[string]queryFn + sudoCallbacks map[string]sudoFn + + // contracts contains a mapping of checksum to code. + storedContracts map[uint32][]byte +} + +// StoreCode implements the WasmEngine interface. +func (m *MockWasmEngine) StoreCode(code wasmvm.WasmCode) (wasmvm.Checksum, error) { + if m.StoreCodeFn == nil { + panic("mock engine is not properly initialized") + } + return m.StoreCodeFn(code) +} + +// Instantiate implements the WasmEngine interface. +func (m *MockWasmEngine) Instantiate(checksum wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + if m.InstantiateFn == nil { + panic("mock engine is not properly initialized") + } + return m.InstantiateFn(checksum, env, info, initMsg, store, goapi, querier, gasMeter, gasLimit, deserCost) +} + +// Query implements the WasmEngine interface. +func (m *MockWasmEngine) Query(checksum wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) ([]byte, uint64, error) { + msgTypeName := getQueryMsgPayloadTypeName(queryMsg) + + callbackFn, ok := m.queryCallbacks[msgTypeName] + if !ok { + panic(fmt.Errorf("no callback specified for %s", msgTypeName)) + } + + return callbackFn(checksum, env, queryMsg, store, goapi, querier, gasMeter, gasLimit, deserCost) +} + +// Migrate implements the WasmEngine interface. +func (m *MockWasmEngine) Migrate(checksum wasmvm.Checksum, env wasmvmtypes.Env, migrateMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + if m.MigrateFn == nil { + panic("mock engine is not properly initialized") + } + return m.MigrateFn(checksum, env, migrateMsg, store, goapi, querier, gasMeter, gasLimit, deserCost) +} + +// Sudo implements the WasmEngine interface. +func (m *MockWasmEngine) Sudo(checksum wasmvm.Checksum, env wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + msgTypeName := getSudoMsgPayloadTypeName(sudoMsg) + + sudoFn, ok := m.sudoCallbacks[msgTypeName] + if !ok { + panic(fmt.Errorf("no callback specified for %s", msgTypeName)) + } + + return sudoFn(checksum, env, sudoMsg, store, goapi, querier, gasMeter, gasLimit, deserCost) +} + +// GetCode implements the WasmEngine interface. +func (m *MockWasmEngine) GetCode(checksum wasmvm.Checksum) (wasmvm.WasmCode, error) { + if m.GetCodeFn == nil { + panic("mock engine is not properly initialized") + } + return m.GetCodeFn(checksum) +} + +// Pin implements the WasmEngine interface. +func (m *MockWasmEngine) Pin(checksum wasmvm.Checksum) error { + if m.PinFn == nil { + panic("mock engine is not properly initialized") + } + return m.PinFn(checksum) +} + +// Unpin implements the WasmEngine interface. +func (m *MockWasmEngine) Unpin(checksum wasmvm.Checksum) error { + if m.UnpinFn == nil { + panic("mock engine is not properly initialized") + } + return m.UnpinFn(checksum) +} + +// getQueryMsgPayloadTypeName extracts the name of the struct that is populated. +// this value is used as a key to map to a callback function to handle that message type. +func getQueryMsgPayloadTypeName(queryMsgBz []byte) string { + payload := types.QueryMsg{} + if err := json.Unmarshal(queryMsgBz, &payload); err != nil { + panic(err) + } + + var payloadField any + if payload.Status != nil { + payloadField = *payload.Status + } + + if payload.CheckForMisbehaviour != nil { + payloadField = *payload.CheckForMisbehaviour + } + + if payload.ExportMetadata != nil { + payloadField = *payload.ExportMetadata + } + + if payload.TimestampAtHeight != nil { + payloadField = *payload.TimestampAtHeight + } + + if payload.VerifyClientMessage != nil { + payloadField = *payload.VerifyClientMessage + } + + if payloadField == nil { + panic(fmt.Errorf("failed to extract valid query message from bytes: %s", string(queryMsgBz))) + } + + return reflect.TypeOf(payloadField).Name() +} + +// getSudoMsgPayloadTypeName extracts the name of the struct that is populated. +// this value is used as a key to map to a callback function to handle that message type. +func getSudoMsgPayloadTypeName(sudoMsgBz []byte) string { + payload := types.SudoMsg{} + if err := json.Unmarshal(sudoMsgBz, &payload); err != nil { + panic(err) + } + + var payloadField any + if payload.UpdateState != nil { + payloadField = *payload.UpdateState + } + + if payload.UpdateStateOnMisbehaviour != nil { + payloadField = *payload.UpdateStateOnMisbehaviour + } + + if payload.VerifyUpgradeAndUpdateState != nil { + payloadField = *payload.VerifyUpgradeAndUpdateState + } + + if payload.VerifyMembership != nil { + payloadField = *payload.VerifyMembership + } + + if payload.VerifyNonMembership != nil { + payloadField = *payload.VerifyNonMembership + } + + if payload.MigrateClientStore != nil { + payloadField = *payload.MigrateClientStore + } + + if payloadField == nil { + panic(fmt.Errorf("failed to extract valid sudo message from bytes: %s", string(sudoMsgBz))) + } + + return reflect.TypeOf(payloadField).Name() +} diff --git a/modules/light-clients/08-wasm/testing/simapp/README.md b/modules/light-clients/08-wasm/testing/simapp/README.md new file mode 100644 index 00000000000..784ce82a0c2 --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/README.md @@ -0,0 +1,5 @@ +# 08-Wasm Testing SimApp + +This testing directory is a duplicate of the ibc-go testing directory. +It is only here as a way of creating a separate SimApp binary to avoid introducing a dependency on the 08-wasm +module from within ibc-go. diff --git a/modules/light-clients/08-wasm/testing/simapp/ante_handler.go b/modules/light-clients/08-wasm/testing/simapp/ante_handler.go new file mode 100644 index 00000000000..098ef9fabbf --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/ante_handler.go @@ -0,0 +1,50 @@ +package simapp + +import ( + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + + ibcante "github.com/cosmos/ibc-go/v8/modules/core/ante" + ibcerrors "github.com/cosmos/ibc-go/v8/modules/core/errors" + "github.com/cosmos/ibc-go/v8/modules/core/keeper" +) + +// HandlerOptions extend the SDK's AnteHandler options by requiring the IBC keeper. +type HandlerOptions struct { + ante.HandlerOptions + + IBCKeeper *keeper.Keeper +} + +// NewAnteHandler creates a new ante handler +func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { + if options.AccountKeeper == nil { + return nil, errorsmod.Wrap(ibcerrors.ErrLogic, "account keeper is required for AnteHandler") + } + if options.BankKeeper == nil { + return nil, errorsmod.Wrap(ibcerrors.ErrLogic, "bank keeper is required for AnteHandler") + } + if options.SignModeHandler == nil { + return nil, errorsmod.Wrap(ibcerrors.ErrLogic, "sign mode handler is required for AnteHandler") + } + + anteDecorators := []sdk.AnteDecorator{ + ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first + ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker), + ante.NewValidateBasicDecorator(), + ante.NewTxTimeoutHeightDecorator(), + ante.NewValidateMemoDecorator(options.AccountKeeper), + ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), + ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker), + ante.NewSetPubKeyDecorator(options.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators + ante.NewValidateSigCountDecorator(options.AccountKeeper), + ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer), + ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler), + ante.NewIncrementSequenceDecorator(options.AccountKeeper), + ibcante.NewRedundantRelayDecorator(options.IBCKeeper), + } + + return sdk.ChainAnteDecorators(anteDecorators...), nil +} diff --git a/modules/light-clients/08-wasm/testing/simapp/app.go b/modules/light-clients/08-wasm/testing/simapp/app.go new file mode 100644 index 00000000000..dc6b30d7e5e --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/app.go @@ -0,0 +1,1105 @@ +package simapp + +import ( + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/gogoproto/proto" + "github.com/spf13/cast" + + autocliv1 "cosmossdk.io/api/cosmos/autocli/v1" + reflectionv1 "cosmossdk.io/api/cosmos/reflection/v1" + "cosmossdk.io/client/v2/autocli" + "cosmossdk.io/core/appmodule" + "cosmossdk.io/log" + storetypes "cosmossdk.io/store/types" + "cosmossdk.io/x/circuit" + circuitkeeper "cosmossdk.io/x/circuit/keeper" + circuittypes "cosmossdk.io/x/circuit/types" + "cosmossdk.io/x/evidence" + evidencekeeper "cosmossdk.io/x/evidence/keeper" + evidencetypes "cosmossdk.io/x/evidence/types" + "cosmossdk.io/x/feegrant" + feegrantkeeper "cosmossdk.io/x/feegrant/keeper" + feegrantmodule "cosmossdk.io/x/feegrant/module" + "cosmossdk.io/x/tx/signing" + "cosmossdk.io/x/upgrade" + upgradekeeper "cosmossdk.io/x/upgrade/keeper" + upgradetypes "cosmossdk.io/x/upgrade/types" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/grpc/cmtservice" + nodeservice "github.com/cosmos/cosmos-sdk/client/grpc/node" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/address" + "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/runtime" + runtimeservices "github.com/cosmos/cosmos-sdk/runtime/services" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/server/api" + "github.com/cosmos/cosmos-sdk/server/config" + servertypes "github.com/cosmos/cosmos-sdk/server/types" + "github.com/cosmos/cosmos-sdk/std" + "github.com/cosmos/cosmos-sdk/testutil/testdata/testpb" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/types/msgservice" + "github.com/cosmos/cosmos-sdk/version" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + "github.com/cosmos/cosmos-sdk/x/auth/posthandler" + authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/auth/vesting" + vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + "github.com/cosmos/cosmos-sdk/x/authz" + authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" + authzmodule "github.com/cosmos/cosmos-sdk/x/authz/module" + "github.com/cosmos/cosmos-sdk/x/bank" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/cosmos/cosmos-sdk/x/consensus" + consensusparamkeeper "github.com/cosmos/cosmos-sdk/x/consensus/keeper" + consensusparamtypes "github.com/cosmos/cosmos-sdk/x/consensus/types" + "github.com/cosmos/cosmos-sdk/x/crisis" + crisiskeeper "github.com/cosmos/cosmos-sdk/x/crisis/keeper" + crisistypes "github.com/cosmos/cosmos-sdk/x/crisis/types" + distr "github.com/cosmos/cosmos-sdk/x/distribution" + distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/cosmos/cosmos-sdk/x/genutil" + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + "github.com/cosmos/cosmos-sdk/x/gov" + govclient "github.com/cosmos/cosmos-sdk/x/gov/client" + govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" + "github.com/cosmos/cosmos-sdk/x/group" + groupkeeper "github.com/cosmos/cosmos-sdk/x/group/keeper" + groupmodule "github.com/cosmos/cosmos-sdk/x/group/module" + "github.com/cosmos/cosmos-sdk/x/mint" + mintkeeper "github.com/cosmos/cosmos-sdk/x/mint/keeper" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + "github.com/cosmos/cosmos-sdk/x/params" + paramsclient "github.com/cosmos/cosmos-sdk/x/params/client" + paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + 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" + + abci "github.com/cometbft/cometbft/abci/types" + + "github.com/cosmos/ibc-go/modules/capability" + capabilitykeeper "github.com/cosmos/ibc-go/modules/capability/keeper" + capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" + wasm "github.com/cosmos/ibc-go/modules/light-clients/08-wasm" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" + wasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" + wasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + ica "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts" + icacontroller "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/controller" + icacontrollerkeeper "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/controller/keeper" + icacontrollertypes "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/controller/types" + icahost "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/host" + icahostkeeper "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/host/keeper" + icahosttypes "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/host/types" + icatypes "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/types" + ibcfee "github.com/cosmos/ibc-go/v8/modules/apps/29-fee" + ibcfeekeeper "github.com/cosmos/ibc-go/v8/modules/apps/29-fee/keeper" + ibcfeetypes "github.com/cosmos/ibc-go/v8/modules/apps/29-fee/types" + transfer "github.com/cosmos/ibc-go/v8/modules/apps/transfer" + ibctransferkeeper "github.com/cosmos/ibc-go/v8/modules/apps/transfer/keeper" + ibctransfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" + ibc "github.com/cosmos/ibc-go/v8/modules/core" + ibcclient "github.com/cosmos/ibc-go/v8/modules/core/02-client" + ibcclienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + ibcconnectiontypes "github.com/cosmos/ibc-go/v8/modules/core/03-connection/types" + porttypes "github.com/cosmos/ibc-go/v8/modules/core/05-port/types" + ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported" + ibckeeper "github.com/cosmos/ibc-go/v8/modules/core/keeper" + solomachine "github.com/cosmos/ibc-go/v8/modules/light-clients/06-solomachine" + ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" + ibcmock "github.com/cosmos/ibc-go/v8/testing/mock" + ibctestingtypes "github.com/cosmos/ibc-go/v8/testing/types" +) + +const appName = "SimApp" + +// IBC application testing ports +const ( + MockFeePort string = ibcmock.ModuleName + ibcfeetypes.ModuleName +) + +var ( + // DefaultNodeHome default home directories for the application daemon + DefaultNodeHome string + + // module account permissions + maccPerms = map[string][]string{ + authtypes.FeeCollectorName: nil, + distrtypes.ModuleName: nil, + minttypes.ModuleName: {authtypes.Minter}, + stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking}, + stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking}, + govtypes.ModuleName: {authtypes.Burner}, + ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + ibcfeetypes.ModuleName: nil, + icatypes.ModuleName: nil, + ibcmock.ModuleName: nil, + } +) + +var ( + _ runtime.AppI = (*SimApp)(nil) + _ servertypes.Application = (*SimApp)(nil) +) + +// SimApp extends an ABCI application, but with most of its parameters exported. +// They are exported for convenience in creating helper functions, as object +// capabilities aren't needed for testing. +type SimApp struct { + *baseapp.BaseApp + legacyAmino *codec.LegacyAmino + appCodec codec.Codec + txConfig client.TxConfig + interfaceRegistry types.InterfaceRegistry + + // keys to access the substores + keys map[string]*storetypes.KVStoreKey + tkeys map[string]*storetypes.TransientStoreKey + memKeys map[string]*storetypes.MemoryStoreKey + + // keepers + AccountKeeper authkeeper.AccountKeeper + BankKeeper bankkeeper.Keeper + CapabilityKeeper *capabilitykeeper.Keeper + StakingKeeper *stakingkeeper.Keeper + SlashingKeeper slashingkeeper.Keeper + MintKeeper mintkeeper.Keeper + DistrKeeper distrkeeper.Keeper + GovKeeper govkeeper.Keeper + CrisisKeeper *crisiskeeper.Keeper + UpgradeKeeper *upgradekeeper.Keeper + ParamsKeeper paramskeeper.Keeper + AuthzKeeper authzkeeper.Keeper + IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly + IBCFeeKeeper ibcfeekeeper.Keeper + ICAControllerKeeper icacontrollerkeeper.Keeper + ICAHostKeeper icahostkeeper.Keeper + EvidenceKeeper evidencekeeper.Keeper + TransferKeeper ibctransferkeeper.Keeper + WasmClientKeeper wasmkeeper.Keeper + FeeGrantKeeper feegrantkeeper.Keeper + GroupKeeper groupkeeper.Keeper + ConsensusParamsKeeper consensusparamkeeper.Keeper + CircuitKeeper circuitkeeper.Keeper + + // make scoped keepers public for test purposes + ScopedIBCKeeper capabilitykeeper.ScopedKeeper + ScopedTransferKeeper capabilitykeeper.ScopedKeeper + ScopedFeeMockKeeper capabilitykeeper.ScopedKeeper + ScopedICAControllerKeeper capabilitykeeper.ScopedKeeper + ScopedICAHostKeeper capabilitykeeper.ScopedKeeper + ScopedIBCMockKeeper capabilitykeeper.ScopedKeeper + ScopedICAMockKeeper capabilitykeeper.ScopedKeeper + + // make IBC modules public for test purposes + // these modules are never directly routed to by the IBC Router + ICAAuthModule ibcmock.IBCModule + FeeMockModule ibcmock.IBCModule + + // the module manager + ModuleManager *module.Manager + BasicModuleManager module.BasicManager + + // simulation manager + simulationManager *module.SimulationManager + + // module configurator + configurator module.Configurator +} + +func init() { + userHomeDir, err := os.UserHomeDir() + if err != nil { + panic(err) + } + + DefaultNodeHome = filepath.Join(userHomeDir, ".simapp") +} + +// NewSimApp returns a reference to an initialized SimApp. +func NewSimApp( + logger log.Logger, + db dbm.DB, + traceStore io.Writer, + loadLatest bool, + appOpts servertypes.AppOptions, + mockVM ibcwasm.WasmEngine, + baseAppOptions ...func(*baseapp.BaseApp), +) *SimApp { + interfaceRegistry, _ := types.NewInterfaceRegistryWithOptions(types.InterfaceRegistryOptions{ + ProtoFiles: proto.HybridResolver, + SigningOptions: signing.Options{ + AddressCodec: address.Bech32Codec{ + Bech32Prefix: sdk.GetConfig().GetBech32AccountAddrPrefix(), + }, + ValidatorAddressCodec: address.Bech32Codec{ + Bech32Prefix: sdk.GetConfig().GetBech32ValidatorAddrPrefix(), + }, + }, + }) + appCodec := codec.NewProtoCodec(interfaceRegistry) + legacyAmino := codec.NewLegacyAmino() + txConfig := authtx.NewTxConfig(appCodec, authtx.DefaultSignModes) + + std.RegisterLegacyAminoCodec(legacyAmino) + std.RegisterInterfaces(interfaceRegistry) + + // Below we could construct and set an application specific mempool and + // ABCI 1.0 PrepareProposal and ProcessProposal handlers. These defaults are + // already set in the SDK's BaseApp, this shows an example of how to override + // them. + // + // Example: + // + // bApp := baseapp.NewBaseApp(...) + // nonceMempool := mempool.NewSenderNonceMempool() + // abciPropHandler := NewDefaultProposalHandler(nonceMempool, bApp) + // + // bApp.SetMempool(nonceMempool) + // bApp.SetPrepareProposal(abciPropHandler.PrepareProposalHandler()) + // bApp.SetProcessProposal(abciPropHandler.ProcessProposalHandler()) + // + // Alternatively, you can construct BaseApp options, append those to + // baseAppOptions and pass them to NewBaseApp. + // + // Example: + // + // prepareOpt = func(app *baseapp.BaseApp) { + // abciPropHandler := baseapp.NewDefaultProposalHandler(nonceMempool, app) + // app.SetPrepareProposal(abciPropHandler.PrepareProposalHandler()) + // } + // baseAppOptions = append(baseAppOptions, prepareOpt) + + bApp := baseapp.NewBaseApp(appName, logger, db, txConfig.TxDecoder(), baseAppOptions...) + bApp.SetCommitMultiStoreTracer(traceStore) + bApp.SetVersion(version.Version) + bApp.SetInterfaceRegistry(interfaceRegistry) + bApp.SetTxEncoder(txConfig.TxEncoder()) + + keys := storetypes.NewKVStoreKeys( + authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, crisistypes.StoreKey, + minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey, + govtypes.StoreKey, group.StoreKey, paramstypes.StoreKey, ibcexported.StoreKey, upgradetypes.StoreKey, feegrant.StoreKey, + evidencetypes.StoreKey, ibctransfertypes.StoreKey, icacontrollertypes.StoreKey, icahosttypes.StoreKey, capabilitytypes.StoreKey, + authzkeeper.StoreKey, ibcfeetypes.StoreKey, consensusparamtypes.StoreKey, circuittypes.StoreKey, wasmtypes.StoreKey, + ) + + // register streaming services + if err := bApp.RegisterStreamingServices(appOpts, keys); err != nil { + panic(err) + } + + tkeys := storetypes.NewTransientStoreKeys(paramstypes.TStoreKey) + memKeys := storetypes.NewMemoryStoreKeys(capabilitytypes.MemStoreKey, ibcmock.MemStoreKey) + + app := &SimApp{ + BaseApp: bApp, + legacyAmino: legacyAmino, + appCodec: appCodec, + txConfig: txConfig, + interfaceRegistry: interfaceRegistry, + keys: keys, + tkeys: tkeys, + memKeys: memKeys, + } + + app.ParamsKeeper = initParamsKeeper(appCodec, legacyAmino, keys[paramstypes.StoreKey], tkeys[paramstypes.TStoreKey]) + + // set the BaseApp's parameter store + app.ConsensusParamsKeeper = consensusparamkeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[consensusparamtypes.StoreKey]), authtypes.NewModuleAddress(govtypes.ModuleName).String(), runtime.EventService{}) + bApp.SetParamStore(app.ConsensusParamsKeeper.ParamsStore) + + // add capability keeper and ScopeToModule for ibc module + app.CapabilityKeeper = capabilitykeeper.NewKeeper(appCodec, keys[capabilitytypes.StoreKey], memKeys[capabilitytypes.MemStoreKey]) + + scopedIBCKeeper := app.CapabilityKeeper.ScopeToModule(ibcexported.ModuleName) + 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 + // Applications that wish to enforce statically created ScopedKeepers should call `Seal` after creating + // their scoped modules in `NewApp` with `ScopeToModule` + app.CapabilityKeeper.Seal() + + // SDK module keepers + + // add keepers + app.AccountKeeper = authkeeper.NewAccountKeeper(appCodec, runtime.NewKVStoreService(keys[authtypes.StoreKey]), authtypes.ProtoBaseAccount, maccPerms, authcodec.NewBech32Codec(sdk.Bech32MainPrefix), sdk.Bech32MainPrefix, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + + app.BankKeeper = bankkeeper.NewBaseKeeper( + appCodec, + runtime.NewKVStoreService(keys[banktypes.StoreKey]), + app.AccountKeeper, + BlockedAddresses(), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + logger, + ) + app.StakingKeeper = stakingkeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[stakingtypes.StoreKey]), app.AccountKeeper, app.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), authcodec.NewBech32Codec(sdk.Bech32PrefixValAddr), authcodec.NewBech32Codec(sdk.Bech32PrefixConsAddr), + ) + app.MintKeeper = mintkeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[minttypes.StoreKey]), app.StakingKeeper, app.AccountKeeper, app.BankKeeper, authtypes.FeeCollectorName, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + + app.DistrKeeper = distrkeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[distrtypes.StoreKey]), app.AccountKeeper, app.BankKeeper, app.StakingKeeper, authtypes.FeeCollectorName, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + + app.SlashingKeeper = slashingkeeper.NewKeeper( + appCodec, legacyAmino, runtime.NewKVStoreService(keys[slashingtypes.StoreKey]), app.StakingKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + invCheckPeriod := cast.ToUint(appOpts.Get(server.FlagInvCheckPeriod)) + app.CrisisKeeper = crisiskeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[crisistypes.StoreKey]), invCheckPeriod, + app.BankKeeper, authtypes.FeeCollectorName, authtypes.NewModuleAddress(govtypes.ModuleName).String(), app.AccountKeeper.AddressCodec()) + + app.FeeGrantKeeper = feegrantkeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[feegrant.StoreKey]), app.AccountKeeper) + + // register the staking hooks + // NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks + app.StakingKeeper.SetHooks( + stakingtypes.NewMultiStakingHooks(app.DistrKeeper.Hooks(), app.SlashingKeeper.Hooks()), + ) + + app.CircuitKeeper = circuitkeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[circuittypes.StoreKey]), authtypes.NewModuleAddress(govtypes.ModuleName).String(), app.AccountKeeper.AddressCodec()) + app.BaseApp.SetCircuitBreaker(&app.CircuitKeeper) + + app.AuthzKeeper = authzkeeper.NewKeeper(runtime.NewKVStoreService(keys[authzkeeper.StoreKey]), appCodec, app.MsgServiceRouter(), app.AccountKeeper) + + groupConfig := group.DefaultConfig() + /* + Example of setting group params: + groupConfig.MaxMetadataLen = 1000 + */ + app.GroupKeeper = groupkeeper.NewKeeper(keys[group.StoreKey], appCodec, app.MsgServiceRouter(), app.AccountKeeper, groupConfig) + + // get skipUpgradeHeights from the app options + skipUpgradeHeights := map[int64]bool{} + for _, h := range cast.ToIntSlice(appOpts.Get(server.FlagUnsafeSkipUpgrades)) { + skipUpgradeHeights[int64(h)] = true + } + homePath := cast.ToString(appOpts.Get(flags.FlagHome)) + // set the governance module account as the authority for conducting upgrades + app.UpgradeKeeper = upgradekeeper.NewKeeper(skipUpgradeHeights, runtime.NewKVStoreService(keys[upgradetypes.StoreKey]), appCodec, homePath, app.BaseApp, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + + app.IBCKeeper = ibckeeper.NewKeeper( + appCodec, keys[ibcexported.StoreKey], app.GetSubspace(ibcexported.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + // Register the proposal types + // Deprecated: Avoid adding new handlers, instead use the new proposal flow + // by granting the governance module the right to execute the message. + // See: https://docs.cosmos.network/main/modules/gov#proposal-messages + govRouter := govv1beta1.NewRouter() + govRouter.AddRoute(govtypes.RouterKey, govv1beta1.ProposalHandler). + AddRoute(paramproposal.RouterKey, params.NewParamChangeProposalHandler(app.ParamsKeeper)). + AddRoute(ibcclienttypes.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper)) + govConfig := govtypes.DefaultConfig() + /* + Example of setting gov params: + govConfig.MaxMetadataLen = 10000 + */ + govKeeper := govkeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[govtypes.StoreKey]), app.AccountKeeper, app.BankKeeper, + app.StakingKeeper, app.DistrKeeper, app.MsgServiceRouter(), govConfig, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // Set legacy router for backwards compatibility with gov v1beta1 + govKeeper.SetLegacyRouter(govRouter) + + app.GovKeeper = *govKeeper.SetHooks( + govtypes.NewMultiGovHooks( + // register the governance hooks + ), + ) + + // 08-wasm's Keeper can be instantiated in two different ways: + // 1. If the chain uses x/wasm: + // Both x/wasm's Keeper and 08-wasm Keeper should share the same Wasm VM instance. + // - Instantiate the Wasm VM in app.go with the parameters of your choice. + // - Create an Option with this Wasm VM instance (see https://github.com/CosmWasm/wasmd/blob/v0.41.0/x/wasm/keeper/options.go#L26-L32). + // - Pass the option to the x/wasm NewKeeper contructor function (https://github.com/CosmWasm/wasmd/blob/v0.41.0/x/wasm/keeper/keeper_cgo.go#L36). + // - Pass a pointer to the Wasm VM instance to 08-wasm NewKeeperWithVM constructor function. + // + // 2. If the chain does not use x/wasm: + // Even though it is still possible to use method 1 above + // (e.g. instantiating a Wasm VM in app.go an pass it in 08-wasm NewKeeper), + // since there is no need to share the Wasm VM instance with another module + // you can use NewKeeperWithConfig constructor function and provide + // the Wasm VM configuration parameters of your choice. + // Check out the WasmConfig type definition for more information on + // each parameter. Some parameters allow node-leve configurations. + // Function DefaultWasmConfig can also be used to use default values. + // + // In the code below we use the second method because we are not using x/wasm in this app.go. + wasmConfig := wasmtypes.WasmConfig{ + DataDir: "ibc_08-wasm_client_data", + SupportedCapabilities: "iterator", + ContractDebugMode: false, + } + if mockVM != nil { + // NOTE: mockVM is used for testing purposes only! + app.WasmClientKeeper = wasmkeeper.NewKeeperWithVM( + appCodec, runtime.NewKVStoreService(keys[wasmtypes.StoreKey]), app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), mockVM, + ) + } else { + app.WasmClientKeeper = wasmkeeper.NewKeeperWithConfig( + appCodec, runtime.NewKVStoreService(keys[wasmtypes.StoreKey]), app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), wasmConfig, + ) + } + + // IBC Fee Module keeper + app.IBCFeeKeeper = ibcfeekeeper.NewKeeper( + appCodec, keys[ibcfeetypes.StoreKey], + app.IBCKeeper.ChannelKeeper, // may be replaced with IBC middleware + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.PortKeeper, app.AccountKeeper, app.BankKeeper, + ) + + // ICA Controller keeper + app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( + appCodec, keys[icacontrollertypes.StoreKey], app.GetSubspace(icacontrollertypes.SubModuleName), + app.IBCFeeKeeper, // use ics29 fee as ics4Wrapper in middleware stack + app.IBCKeeper.ChannelKeeper, app.IBCKeeper.PortKeeper, + scopedICAControllerKeeper, app.MsgServiceRouter(), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // ICA Host keeper + app.ICAHostKeeper = icahostkeeper.NewKeeper( + appCodec, keys[icahosttypes.StoreKey], app.GetSubspace(icahosttypes.SubModuleName), + app.IBCFeeKeeper, // use ics29 fee as ics4Wrapper in middleware stack + app.IBCKeeper.ChannelKeeper, app.IBCKeeper.PortKeeper, + app.AccountKeeper, scopedICAHostKeeper, app.MsgServiceRouter(), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // 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, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // Mock Module Stack + + // Mock Module setup for testing IBC and also acts as the interchain accounts authentication module + // NOTE: the IBC mock keeper and application module is used only for testing core IBC. Do + // not replicate if you do not need to test core IBC or light clients. + mockModule := ibcmock.NewAppModule(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: + // icaControllerKeeper.SendTx -> 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, mock module cannot send packets + + // 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, runtime.NewKVStoreService(keys[evidencetypes.StoreKey]), app.StakingKeeper, app.SlashingKeeper, app.AccountKeeper.AddressCodec(), runtime.ProvideCometInfoService(), + ) + // If evidence needs to be handled for the app, set routes in router here and seal + app.EvidenceKeeper = *evidenceKeeper + + // **** Module Options **** + + // NOTE: we may consider parsing `appOpts` inside module constructors. For the moment + // we prefer to be more strict in what arguments the modules expect. + skipGenesisInvariants := cast.ToBool(appOpts.Get(crisis.FlagSkipGenesisInvariants)) + + // NOTE: Any module instantiated in the module manager that is later modified + // must be passed by reference here. + app.ModuleManager = module.NewManager( + genutil.NewAppModule( + app.AccountKeeper, app.StakingKeeper, app, + txConfig, + ), + auth.NewAppModule(appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts, app.GetSubspace(authtypes.ModuleName)), + vesting.NewAppModule(app.AccountKeeper, app.BankKeeper), + bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper, app.GetSubspace(banktypes.ModuleName)), + capability.NewAppModule(appCodec, *app.CapabilityKeeper, false), + crisis.NewAppModule(app.CrisisKeeper, skipGenesisInvariants, app.GetSubspace(crisistypes.ModuleName)), + feegrantmodule.NewAppModule(appCodec, app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, app.interfaceRegistry), + gov.NewAppModule(appCodec, &app.GovKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(govtypes.ModuleName)), + mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper, nil, app.GetSubspace(minttypes.ModuleName)), + slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(slashingtypes.ModuleName), app.interfaceRegistry), + distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(distrtypes.ModuleName)), + staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(stakingtypes.ModuleName)), + upgrade.NewAppModule(app.UpgradeKeeper, app.AccountKeeper.AddressCodec()), + evidence.NewAppModule(app.EvidenceKeeper), + params.NewAppModule(app.ParamsKeeper), + authzmodule.NewAppModule(appCodec, app.AuthzKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), + groupmodule.NewAppModule(appCodec, app.GroupKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), + consensus.NewAppModule(appCodec, app.ConsensusParamsKeeper), + circuit.NewAppModule(appCodec, app.CircuitKeeper), + + // IBC modules + ibc.NewAppModule(app.IBCKeeper), + transfer.NewAppModule(app.TransferKeeper), + ibcfee.NewAppModule(app.IBCFeeKeeper), + ica.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper), + wasm.NewAppModule(app.WasmClientKeeper), + ibctm.AppModuleBasic{}, + solomachine.AppModuleBasic{}, + mockModule, + ) + + // BasicModuleManager defines the module BasicManager is in charge of setting up basic, + // non-dependant module elements, such as codec registration and genesis verification. + // By default it is composed of all the module from the module manager. + // Additionally, app module basics can be overwritten by passing them as argument. + app.BasicModuleManager = module.NewBasicManagerFromManager( + app.ModuleManager, + map[string]module.AppModuleBasic{ + genutiltypes.ModuleName: genutil.NewAppModuleBasic(genutiltypes.DefaultMessageValidator), + govtypes.ModuleName: gov.NewAppModuleBasic( + []govclient.ProposalHandler{ + paramsclient.ProposalHandler, + }, + ), + }) + app.BasicModuleManager.RegisterLegacyAminoCodec(legacyAmino) + app.BasicModuleManager.RegisterInterfaces(interfaceRegistry) + + // NOTE: upgrade module is required to be prioritized + app.ModuleManager.SetOrderPreBlockers( + upgradetypes.ModuleName, + ) + + // 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.ModuleManager.SetOrderBeginBlockers( + capabilitytypes.ModuleName, + minttypes.ModuleName, + distrtypes.ModuleName, + slashingtypes.ModuleName, + evidencetypes.ModuleName, + stakingtypes.ModuleName, + ibcexported.ModuleName, + ibctransfertypes.ModuleName, + genutiltypes.ModuleName, + authz.ModuleName, + icatypes.ModuleName, + ibcfeetypes.ModuleName, + wasmtypes.ModuleName, + ibcmock.ModuleName, + ) + app.ModuleManager.SetOrderEndBlockers( + crisistypes.ModuleName, + govtypes.ModuleName, + stakingtypes.ModuleName, + ibcexported.ModuleName, + ibctransfertypes.ModuleName, + capabilitytypes.ModuleName, + genutiltypes.ModuleName, + feegrant.ModuleName, + icatypes.ModuleName, + ibcfeetypes.ModuleName, + wasmtypes.ModuleName, + ibcmock.ModuleName, + group.ModuleName, + ) + + // NOTE: The genutils module must occur after staking so that pools are + // properly initialized with tokens from genesis accounts. + // NOTE: The genutils module must also occur after auth so that it can access the params from auth. + // 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. + genesisModuleOrder := []string{ + capabilitytypes.ModuleName, + authtypes.ModuleName, + banktypes.ModuleName, distrtypes.ModuleName, stakingtypes.ModuleName, + slashingtypes.ModuleName, govtypes.ModuleName, minttypes.ModuleName, crisistypes.ModuleName, + ibcexported.ModuleName, genutiltypes.ModuleName, evidencetypes.ModuleName, authz.ModuleName, ibctransfertypes.ModuleName, + icatypes.ModuleName, ibcfeetypes.ModuleName, ibcmock.ModuleName, feegrant.ModuleName, paramstypes.ModuleName, upgradetypes.ModuleName, + vestingtypes.ModuleName, group.ModuleName, consensusparamtypes.ModuleName, circuittypes.ModuleName, wasmtypes.ModuleName, + } + app.ModuleManager.SetOrderInitGenesis(genesisModuleOrder...) + app.ModuleManager.SetOrderExportGenesis(genesisModuleOrder...) + + // Uncomment if you want to set a custom migration order here. + // app.ModuleManager.SetOrderMigrations(custom order) + + app.ModuleManager.RegisterInvariants(app.CrisisKeeper) + app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter()) + err := app.ModuleManager.RegisterServices(app.configurator) + if err != nil { + panic(err) + } + + autocliv1.RegisterQueryServer(app.GRPCQueryRouter(), runtimeservices.NewAutoCLIQueryService(app.ModuleManager.Modules)) + + reflectionSvc, err := runtimeservices.NewReflectionService() + if err != nil { + panic(err) + } + reflectionv1.RegisterReflectionServiceServer(app.GRPCQueryRouter(), reflectionSvc) + + // add test gRPC service for testing gRPC queries in isolation + testpb.RegisterQueryServer(app.GRPCQueryRouter(), testpb.QueryImpl{}) + + // create the simulation manager and define the order of the modules for deterministic simulations + // + // NOTE: this is not required apps that don't use the simulator for fuzz testing + // transactions + overrideModules := map[string]module.AppModuleSimulation{ + authtypes.ModuleName: auth.NewAppModule(app.appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts, app.GetSubspace(authtypes.ModuleName)), + } + app.simulationManager = module.NewSimulationManagerFromAppModules(app.ModuleManager.Modules, overrideModules) + + app.simulationManager.RegisterStoreDecoders() + + // initialize stores + app.MountKVStores(keys) + app.MountTransientStores(tkeys) + app.MountMemoryStores(memKeys) + + // initialize BaseApp + app.SetInitChainer(app.InitChainer) + app.SetPreBlocker(app.PreBlocker) + app.SetBeginBlocker(app.BeginBlocker) + app.SetEndBlocker(app.EndBlocker) + app.setAnteHandler(txConfig) + + // must be before Loading version + if manager := app.SnapshotManager(); manager != nil { + err := manager.RegisterExtensions( + wasmkeeper.NewWasmSnapshotter(app.CommitMultiStore(), &app.WasmClientKeeper), + ) + if err != nil { + panic(fmt.Errorf("failed to register snapshot extension: %s", err)) + } + } + + // In v0.46, the SDK introduces _postHandlers_. PostHandlers are like + // antehandlers, but are run _after_ the `runMsgs` execution. They are also + // defined as a chain, and have the same signature as antehandlers. + // + // In baseapp, postHandlers are run in the same store branch as `runMsgs`, + // meaning that both `runMsgs` and `postHandler` state will be committed if + // both are successful, and both will be reverted if any of the two fails. + // + // The SDK exposes a default postHandlers chain, which comprises of only + // one decorator: the Transaction Tips decorator. However, some chains do + // not need it by default, so feel free to comment the next line if you do + // not need tips. + // To read more about tips: + // https://docs.cosmos.network/main/core/tips.html + // + // Please note that changing any of the anteHandler or postHandler chain is + // likely to be a state-machine breaking change, which needs a coordinated + // upgrade. + app.setPostHandler() + + // At startup, after all modules have been registered, check that all proto + // annotations are correct. + protoFiles, err := proto.MergedRegistry() + if err != nil { + panic(err) + } + err = msgservice.ValidateProtoAnnotations(protoFiles) + if err != nil { + // Once we switch to using protoreflect-based antehandlers, we might + // want to panic here instead of logging a warning. + _, err := fmt.Fprintln(os.Stderr, err.Error()) + if err != nil { + fmt.Println("could not write to stderr") + } + } + + if loadLatest { + if err := app.LoadLatestVersion(); err != nil { + panic(fmt.Errorf("error loading last version: %w", err)) + } + } + + 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 +} + +func (app *SimApp) setAnteHandler(txConfig client.TxConfig) { + anteHandler, err := NewAnteHandler( + HandlerOptions{ + HandlerOptions: ante.HandlerOptions{ + AccountKeeper: app.AccountKeeper, + BankKeeper: app.BankKeeper, + FeegrantKeeper: app.FeeGrantKeeper, + SignModeHandler: txConfig.SignModeHandler(), + SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + }, + IBCKeeper: app.IBCKeeper, + }, + ) + if err != nil { + panic(err) + } + + // Set the AnteHandler for the app + app.SetAnteHandler(anteHandler) +} + +func (app *SimApp) setPostHandler() { + postHandler, err := posthandler.NewPostHandler( + posthandler.HandlerOptions{}, + ) + if err != nil { + panic(err) + } + + app.SetPostHandler(postHandler) +} + +// Name returns the name of the App +func (app *SimApp) Name() string { return app.BaseApp.Name() } + +// PreBlocker application updates every pre block +func (app *SimApp) PreBlocker(ctx sdk.Context, _ *abci.RequestFinalizeBlock) (*sdk.ResponsePreBlock, error) { + return app.ModuleManager.PreBlock(ctx) +} + +// BeginBlocker application updates every begin block +func (app *SimApp) BeginBlocker(ctx sdk.Context) (sdk.BeginBlock, error) { + return app.ModuleManager.BeginBlock(ctx) +} + +// EndBlocker application updates every end block +func (app *SimApp) EndBlocker(ctx sdk.Context) (sdk.EndBlock, error) { + return app.ModuleManager.EndBlock(ctx) +} + +// Configurator returns the configurator for the app +func (app *SimApp) Configurator() module.Configurator { + return app.configurator +} + +// InitChainer application update at chain initialization +func (app *SimApp) InitChainer(ctx sdk.Context, req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { + var genesisState GenesisState + if err := json.Unmarshal(req.AppStateBytes, &genesisState); err != nil { + panic(err) + } + if err := app.UpgradeKeeper.SetModuleVersionMap(ctx, app.ModuleManager.GetVersionMap()); err != nil { + panic(err) + } + return app.ModuleManager.InitGenesis(ctx, app.appCodec, genesisState) +} + +// LoadHeight loads a particular height +func (app *SimApp) LoadHeight(height int64) error { + return app.LoadVersion(height) +} + +// LegacyAmino returns SimApp's amino codec. +// +// NOTE: This is solely to be used for testing purposes as it may be desirable +// for modules to register their own custom testing types. +func (app *SimApp) LegacyAmino() *codec.LegacyAmino { + return app.legacyAmino +} + +// AppCodec returns SimApp's app codec. +// +// NOTE: This is solely to be used for testing purposes as it may be desirable +// for modules to register their own custom testing types. +func (app *SimApp) AppCodec() codec.Codec { + return app.appCodec +} + +// InterfaceRegistry returns SimApp's InterfaceRegistry +func (app *SimApp) InterfaceRegistry() types.InterfaceRegistry { + return app.interfaceRegistry +} + +// TxConfig returns SimApp's TxConfig +func (app *SimApp) TxConfig() client.TxConfig { + return app.txConfig +} + +// AutoCliOpts returns the autocli options for the app. +func (app *SimApp) AutoCliOpts() autocli.AppOptions { + modules := make(map[string]appmodule.AppModule, 0) + for _, m := range app.ModuleManager.Modules { + if moduleWithName, ok := m.(module.HasName); ok { + moduleName := moduleWithName.Name() + if appModule, ok := moduleWithName.(appmodule.AppModule); ok { + modules[moduleName] = appModule + } + } + } + + return autocli.AppOptions{ + Modules: modules, + ModuleOptions: runtimeservices.ExtractAutoCLIOptions(app.ModuleManager.Modules), + } +} + +// DefaultGenesis returns a default genesis from the registered AppModuleBasic's. +func (app *SimApp) DefaultGenesis() map[string]json.RawMessage { + return app.BasicModuleManager.DefaultGenesis(app.appCodec) +} + +// GetKey returns the KVStoreKey for the provided store key. +// +// NOTE: This is solely to be used for testing purposes. +func (app *SimApp) GetKey(storeKey string) *storetypes.KVStoreKey { + return app.keys[storeKey] +} + +// GetStoreKeys returns all the stored store keys. +func (app *SimApp) GetStoreKeys() []storetypes.StoreKey { + keys := make([]storetypes.StoreKey, len(app.keys)) + for _, key := range app.keys { + keys = append(keys, key) + } + + return keys +} + +// GetSubspace returns a param subspace for a given module name. +// +// NOTE: This is solely to be used for testing purposes. +func (app *SimApp) GetSubspace(moduleName string) paramstypes.Subspace { + subspace, _ := app.ParamsKeeper.GetSubspace(moduleName) + return subspace +} + +// SimulationManager implements the SimulationApp interface +func (app *SimApp) SimulationManager() *module.SimulationManager { + return app.simulationManager +} + +// RegisterAPIRoutes registers all application module routes with the provided +// API server. +func (app *SimApp) RegisterAPIRoutes(apiSvr *api.Server, apiConfig config.APIConfig) { + clientCtx := apiSvr.ClientCtx + // Register new tx routes from grpc-gateway. + authtx.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + + // Register new CometBFT queries routes from grpc-gateway. + cmtservice.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + + // Register node gRPC service for grpc-gateway. + nodeservice.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + + // Register grpc-gateway routes for all modules. + app.BasicModuleManager.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + + // register swagger API from root so that other applications can override easily + if err := server.RegisterSwaggerAPI(apiSvr.ClientCtx, apiSvr.Router, apiConfig.Swagger); err != nil { + panic(err) + } +} + +// RegisterTxService implements the Application.RegisterTxService method. +func (app *SimApp) RegisterTxService(clientCtx client.Context) { + authtx.RegisterTxService(app.BaseApp.GRPCQueryRouter(), clientCtx, app.BaseApp.Simulate, app.interfaceRegistry) +} + +// RegisterTendermintService implements the Application.RegisterTendermintService method. +func (app *SimApp) RegisterTendermintService(clientCtx client.Context) { + cmtApp := server.NewCometABCIWrapper(app) + cmtservice.RegisterTendermintService( + clientCtx, + app.BaseApp.GRPCQueryRouter(), + app.interfaceRegistry, + cmtApp.Query, + ) +} + +func (app *SimApp) RegisterNodeService(clientCtx client.Context, cfg config.Config) { + nodeservice.RegisterNodeService(clientCtx, app.GRPCQueryRouter(), cfg) +} + +// GetMaccPerms returns a copy of the module account permissions +// +// NOTE: This is solely to be used for testing purposes. +func GetMaccPerms() map[string][]string { + dupMaccPerms := make(map[string][]string) + for k, v := range maccPerms { + dupMaccPerms[k] = v + } + + return dupMaccPerms +} + +// BlockedAddresses returns all the app's blocked account addresses. +func BlockedAddresses() map[string]bool { + modAccAddrs := make(map[string]bool) + for acc := range GetMaccPerms() { + modAccAddrs[authtypes.NewModuleAddress(acc).String()] = true + } + + // allow the following addresses to receive funds + delete(modAccAddrs, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + delete(modAccAddrs, authtypes.NewModuleAddress(ibcmock.ModuleName).String()) + + return modAccAddrs +} + +// initParamsKeeper init params keeper and its subspaces +func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey storetypes.StoreKey) paramskeeper.Keeper { + paramsKeeper := paramskeeper.NewKeeper(appCodec, legacyAmino, key, tkey) + + // register the key tables for legacy param subspaces + keyTable := ibcclienttypes.ParamKeyTable() + keyTable.RegisterParamSet(&ibcconnectiontypes.Params{}) + paramsKeeper.Subspace(ibcexported.ModuleName).WithKeyTable(keyTable) + paramsKeeper.Subspace(ibctransfertypes.ModuleName).WithKeyTable(ibctransfertypes.ParamKeyTable()) + paramsKeeper.Subspace(icacontrollertypes.SubModuleName).WithKeyTable(icacontrollertypes.ParamKeyTable()) + paramsKeeper.Subspace(icahosttypes.SubModuleName).WithKeyTable(icahosttypes.ParamKeyTable()) + + return paramsKeeper +} + +// IBC TestingApp functions + +// GetBaseApp implements the TestingApp interface. +func (app *SimApp) GetBaseApp() *baseapp.BaseApp { + return app.BaseApp +} + +// GetStakingKeeper implements the TestingApp interface. +func (app *SimApp) GetStakingKeeper() ibctestingtypes.StakingKeeper { + return app.StakingKeeper +} + +// GetIBCKeeper implements the TestingApp interface. +func (app *SimApp) GetIBCKeeper() *ibckeeper.Keeper { + return app.IBCKeeper +} + +// GetWasmKeeper implements the TestingApp interface. +func (app *SimApp) GetWasmKeeper() wasmkeeper.Keeper { + return app.WasmClientKeeper +} + +// GetScopedIBCKeeper implements the TestingApp interface. +func (app *SimApp) GetScopedIBCKeeper() capabilitykeeper.ScopedKeeper { + return app.ScopedIBCKeeper +} + +// GetTxConfig implements the TestingApp interface. +func (app *SimApp) GetTxConfig() client.TxConfig { + return app.txConfig +} + +// GetMemKey returns the MemStoreKey for the provided mem key. +// +// NOTE: This is solely used for testing purposes. +func (app *SimApp) GetMemKey(storeKey string) *storetypes.MemoryStoreKey { + return app.memKeys[storeKey] +} diff --git a/modules/light-clients/08-wasm/testing/simapp/encoding.go b/modules/light-clients/08-wasm/testing/simapp/encoding.go new file mode 100644 index 00000000000..ba2d01ecbcf --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/encoding.go @@ -0,0 +1,18 @@ +package simapp + +import ( + "github.com/cosmos/cosmos-sdk/std" + + simappparams "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing/simapp/params" +) + +// MakeTestEncodingConfig creates an EncodingConfig for testing. This function +// should be used only in tests or when creating a new app instance (NewApp*()). +// App user shouldn't create new codecs - use the app.AppCodec instead. +// [DEPRECATED] +func MakeTestEncodingConfig() simappparams.EncodingConfig { + encodingConfig := simappparams.MakeTestEncodingConfig() + std.RegisterLegacyAminoCodec(encodingConfig.Amino) + std.RegisterInterfaces(encodingConfig.InterfaceRegistry) + return encodingConfig +} diff --git a/modules/light-clients/08-wasm/testing/simapp/export.go b/modules/light-clients/08-wasm/testing/simapp/export.go new file mode 100644 index 00000000000..871b15cb41c --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/export.go @@ -0,0 +1,249 @@ +package simapp + +import ( + "encoding/json" + "errors" + "log" + + storetypes "cosmossdk.io/store/types" + + servertypes "github.com/cosmos/cosmos-sdk/server/types" + sdk "github.com/cosmos/cosmos-sdk/types" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +// ExportAppStateAndValidators exports the state of the application for a genesis +// file. +func (app *SimApp) ExportAppStateAndValidators( + forZeroHeight bool, jailAllowedAddrs []string, modulesToExport []string, +) (servertypes.ExportedApp, error) { + // as if they could withdraw from the start of the next block + ctx := app.NewContext(true) + + // We export at last height + 1, because that's the height at which + // Tendermint will start InitChain. + height := app.LastBlockHeight() + 1 + if forZeroHeight { + height = 0 + app.prepForZeroHeightGenesis(ctx, jailAllowedAddrs) + } + + genState, err := app.ModuleManager.ExportGenesis(ctx, app.appCodec) + if err != nil { + return servertypes.ExportedApp{}, err + } + appState, err := json.MarshalIndent(genState, "", " ") + if err != nil { + return servertypes.ExportedApp{}, err + } + + validators, err := staking.WriteValidators(ctx, app.StakingKeeper) + return servertypes.ExportedApp{ + AppState: appState, + Validators: validators, + Height: height, + ConsensusParams: app.BaseApp.GetConsensusParams(ctx), + }, err +} + +// prepare for fresh start at zero height +// NOTE zero height genesis is a temporary feature which will be deprecated +// in favour of export at a block height +func (app *SimApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs []string) { + applyAllowedAddrs := false + + // check if there is a allowed address list + if len(jailAllowedAddrs) > 0 { + applyAllowedAddrs = true + } + + allowedAddrsMap := make(map[string]bool) + + for _, addr := range jailAllowedAddrs { + _, err := sdk.ValAddressFromBech32(addr) + if err != nil { + log.Fatal(err) + } + allowedAddrsMap[addr] = true + } + + /* Just to be safe, assert the invariants on current state. */ + app.CrisisKeeper.AssertInvariants(ctx) + + /* Handle fee distribution state. */ + + // withdraw all validator commission + err := app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { + valBz, err := app.StakingKeeper.ValidatorAddressCodec().StringToBytes(val.GetOperator()) + if err != nil { + panic(err) + } + _, _ = app.DistrKeeper.WithdrawValidatorCommission(ctx, valBz) + return false + }) + if err != nil { + panic(err) + } + + // withdraw all delegator rewards + dels, err := app.StakingKeeper.GetAllDelegations(ctx) + if err != nil { + panic(err) + } + for _, delegation := range dels { + valAddr, err := sdk.ValAddressFromBech32(delegation.ValidatorAddress) + if err != nil { + panic(err) + } + + delAddr, err := sdk.AccAddressFromBech32(delegation.DelegatorAddress) + if err != nil { + panic(err) + } + _, _ = app.DistrKeeper.WithdrawDelegationRewards(ctx, delAddr, valAddr) + } + + // clear validator slash events + app.DistrKeeper.DeleteAllValidatorSlashEvents(ctx) + + // clear validator historical rewards + app.DistrKeeper.DeleteAllValidatorHistoricalRewards(ctx) + + // set context height to zero + height := ctx.BlockHeight() + ctx = ctx.WithBlockHeight(0) + + // reinitialize all validators + err = app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { + valBz, err := sdk.ValAddressFromBech32(val.GetOperator()) + if err != nil { + panic(err) + } + // donate any unwithdrawn outstanding reward fraction tokens to the community pool + scraps, err := app.DistrKeeper.GetValidatorOutstandingRewardsCoins(ctx, valBz) + if err != nil { + panic(err) + } + feePool, err := app.DistrKeeper.FeePool.Get(ctx) + if err != nil { + panic(err) + } + feePool.CommunityPool = feePool.CommunityPool.Add(scraps...) + if err := app.DistrKeeper.FeePool.Set(ctx, feePool); err != nil { + panic(err) + } + + if err := app.DistrKeeper.Hooks().AfterValidatorCreated(ctx, valBz); err != nil { + panic(err) + } + return false + }) + if err != nil { + panic(err) + } + + // reinitialize all delegations + for _, del := range dels { + valAddr, err := sdk.ValAddressFromBech32(del.ValidatorAddress) + if err != nil { + panic(err) + } + delAddr, err := sdk.AccAddressFromBech32(del.DelegatorAddress) + if err != nil { + panic(err) + } + err = app.DistrKeeper.Hooks().BeforeDelegationCreated(ctx, delAddr, valAddr) + if err != nil { + panic(err) + } + err = app.DistrKeeper.Hooks().AfterDelegationModified(ctx, delAddr, valAddr) + if err != nil { + panic(err) + } + } + + // reset context height + ctx = ctx.WithBlockHeight(height) + + /* Handle staking state. */ + + // iterate through redelegations, reset creation height + err = app.StakingKeeper.IterateRedelegations(ctx, func(_ int64, red stakingtypes.Redelegation) (stop bool) { + for i := range red.Entries { + red.Entries[i].CreationHeight = 0 + } + err := app.StakingKeeper.SetRedelegation(ctx, red) + if err != nil { + panic(err) + } + return false + }) + if err != nil { + panic(err) + } + + // iterate through unbonding delegations, reset creation height + err = app.StakingKeeper.IterateUnbondingDelegations(ctx, func(_ int64, ubd stakingtypes.UnbondingDelegation) (stop bool) { + for i := range ubd.Entries { + ubd.Entries[i].CreationHeight = 0 + } + + if err := app.StakingKeeper.SetUnbondingDelegation(ctx, ubd); err != nil { + panic(err) + } + return false + }) + if err != nil { + panic(err) + } + + // Iterate through validators by power descending, reset bond heights, and + // update bond intra-tx counters. + store := ctx.KVStore(app.keys[stakingtypes.StoreKey]) + iter := storetypes.KVStoreReversePrefixIterator(store, stakingtypes.ValidatorsKey) + counter := int16(0) + + for ; iter.Valid(); iter.Next() { + addr := sdk.ValAddress(stakingtypes.AddressFromValidatorsKey(iter.Key())) + validator, err := app.StakingKeeper.GetValidator(ctx, addr) + if err != nil { + panic(errors.New("expected validator, not found")) + } + + validator.UnbondingHeight = 0 + if applyAllowedAddrs && !allowedAddrsMap[addr.String()] { + validator.Jailed = true + } + + if err := app.StakingKeeper.SetValidator(ctx, validator); err != nil { + panic(err) + } + counter++ + } + + iter.Close() + + _, err = app.StakingKeeper.ApplyAndReturnValidatorSetUpdates(ctx) + if err != nil { + log.Fatal(err) + } + + /* Handle slashing state. */ + + // reset start height on signing infos + err = app.SlashingKeeper.IterateValidatorSigningInfos( + ctx, + func(addr sdk.ConsAddress, info slashingtypes.ValidatorSigningInfo) (stop bool) { + info.StartHeight = 0 + if err := app.SlashingKeeper.SetValidatorSigningInfo(ctx, addr, info); err != nil { + panic(err) + } + return false + }, + ) + if err != nil { + panic(err) + } +} diff --git a/modules/light-clients/08-wasm/testing/simapp/genesis.go b/modules/light-clients/08-wasm/testing/simapp/genesis.go new file mode 100644 index 00000000000..2667b97a4fc --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/genesis.go @@ -0,0 +1,14 @@ +package simapp + +import ( + "encoding/json" +) + +// The genesis state of the blockchain is represented here as a map of raw json +// messages key'd by a identifier string. +// The identifier is used to determine which module genesis information belongs +// to so it may be appropriately routed during init chain. +// Within this application default genesis information is retrieved from +// the ModuleBasicManager which populates json from each BasicModule +// object provided to it during init. +type GenesisState map[string]json.RawMessage diff --git a/modules/light-clients/08-wasm/testing/simapp/genesis_account.go b/modules/light-clients/08-wasm/testing/simapp/genesis_account.go new file mode 100644 index 00000000000..5c9c7f9a03f --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/genesis_account.go @@ -0,0 +1,47 @@ +package simapp + +import ( + "errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +var _ authtypes.GenesisAccount = (*SimGenesisAccount)(nil) + +// SimGenesisAccount defines a type that implements the GenesisAccount interface +// to be used for simulation accounts in the genesis state. +type SimGenesisAccount struct { + *authtypes.BaseAccount + + // vesting account fields + OriginalVesting sdk.Coins `json:"original_vesting" yaml:"original_vesting"` // total vesting coins upon initialization + DelegatedFree sdk.Coins `json:"delegated_free" yaml:"delegated_free"` // delegated vested coins at time of delegation + DelegatedVesting sdk.Coins `json:"delegated_vesting" yaml:"delegated_vesting"` // delegated vesting coins at time of delegation + StartTime int64 `json:"start_time" yaml:"start_time"` // vesting start time (UNIX Epoch time) + EndTime int64 `json:"end_time" yaml:"end_time"` // vesting end time (UNIX Epoch time) + + // module account fields + ModuleName string `json:"module_name" yaml:"module_name"` // name of the module account + ModulePermissions []string `json:"module_permissions" yaml:"module_permissions"` // permissions of module account +} + +// Validate checks for errors on the vesting and module account parameters +func (sga SimGenesisAccount) Validate() error { + if !sga.OriginalVesting.IsZero() { + if sga.StartTime >= sga.EndTime { + return errors.New("vesting start-time cannot be before end-time") + } + } + + if sga.ModuleName != "" { + ma := authtypes.ModuleAccount{ + BaseAccount: sga.BaseAccount, Name: sga.ModuleName, Permissions: sga.ModulePermissions, + } + if err := ma.Validate(); err != nil { + return err + } + } + + return sga.BaseAccount.Validate() +} diff --git a/modules/light-clients/08-wasm/testing/simapp/params/amino.go b/modules/light-clients/08-wasm/testing/simapp/params/amino.go new file mode 100644 index 00000000000..d603987dd5f --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/params/amino.go @@ -0,0 +1,27 @@ +//go:build test_amino +// +build test_amino + +package params + +import ( + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" +) + +// MakeTestEncodingConfig creates an EncodingConfig for an amino based test configuration. +// This function should be used only internally (in the SDK). +// App user should'nt create new codecs - use the app.AppCodec instead. +// [DEPRECATED] +func MakeTestEncodingConfig() EncodingConfig { + cdc := codec.NewLegacyAmino() + interfaceRegistry := types.NewInterfaceRegistry() + marshaler := codec.NewAminoCodec(cdc) + + return EncodingConfig{ + InterfaceRegistry: interfaceRegistry, + Marshaler: marshaler, + TxConfig: legacytx.StdTxConfig{Cdc: cdc}, + Amino: cdc, + } +} diff --git a/modules/light-clients/08-wasm/testing/simapp/params/doc.go b/modules/light-clients/08-wasm/testing/simapp/params/doc.go new file mode 100644 index 00000000000..a2f3620a835 --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/params/doc.go @@ -0,0 +1,19 @@ +/* +Package params defines the simulation parameters in the simapp. + +It contains the default weights used for each transaction used on the module's +simulation. These weights define the chance for a transaction to be simulated at +any given operation. + +You can replace the default values for the weights by providing a params.json +file with the weights defined for each of the transaction operations: + + { + "op_weight_msg_send": 60, + "op_weight_msg_delegate": 100, + } + +In the example above, the `MsgSend` has 60% chance to be simulated, while the +`MsgDelegate` will always be simulated. +*/ +package params diff --git a/modules/light-clients/08-wasm/testing/simapp/params/encoding.go b/modules/light-clients/08-wasm/testing/simapp/params/encoding.go new file mode 100644 index 00000000000..8ff9ea04b39 --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/params/encoding.go @@ -0,0 +1,16 @@ +package params + +import ( + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/types" +) + +// EncodingConfig specifies the concrete encoding types to use for a given app. +// This is provided for compatibility between protobuf and amino implementations. +type EncodingConfig struct { + InterfaceRegistry types.InterfaceRegistry + Codec codec.Codec + TxConfig client.TxConfig + Amino *codec.LegacyAmino +} diff --git a/modules/light-clients/08-wasm/testing/simapp/params/proto.go b/modules/light-clients/08-wasm/testing/simapp/params/proto.go new file mode 100644 index 00000000000..4270c2b0dbf --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/params/proto.go @@ -0,0 +1,27 @@ +//go:build !test_amino +// +build !test_amino + +package params + +import ( + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/x/auth/tx" +) + +// MakeTestEncodingConfig creates an EncodingConfig for a non-amino based test configuration. +// This function should be used only internally (in the SDK). +// App user should'nt create new codecs - use the app.AppCodec instead. +// [DEPRECATED] +func MakeTestEncodingConfig() EncodingConfig { + cdc := codec.NewLegacyAmino() + interfaceRegistry := types.NewInterfaceRegistry() + protoCdc := codec.NewProtoCodec(interfaceRegistry) + + return EncodingConfig{ + InterfaceRegistry: interfaceRegistry, + Codec: protoCdc, + TxConfig: tx.NewTxConfig(protoCdc, tx.DefaultSignModes), + Amino: cdc, + } +} diff --git a/modules/light-clients/08-wasm/testing/simapp/simd/cmd/root.go b/modules/light-clients/08-wasm/testing/simapp/simd/cmd/root.go new file mode 100644 index 00000000000..615728f7baf --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/simd/cmd/root.go @@ -0,0 +1,428 @@ +package cmd + +import ( + "errors" + "fmt" + "io" + "os" + "runtime/debug" + "strings" + + wasmvm "github.com/CosmWasm/wasmvm" + dbm "github.com/cosmos/cosmos-db" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "cosmossdk.io/client/v2/autocli" + "cosmossdk.io/log" + confixcmd "cosmossdk.io/tools/confix/cmd" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/config" + sdkdebug "github.com/cosmos/cosmos-sdk/client/debug" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/client/pruning" + "github.com/cosmos/cosmos-sdk/client/rpc" + "github.com/cosmos/cosmos-sdk/client/snapshot" + "github.com/cosmos/cosmos-sdk/codec" + addresscodec "github.com/cosmos/cosmos-sdk/codec/address" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/server" + serverconfig "github.com/cosmos/cosmos-sdk/server/config" + servertypes "github.com/cosmos/cosmos-sdk/server/types" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + "github.com/cosmos/cosmos-sdk/x/auth/tx" + txmodule "github.com/cosmos/cosmos-sdk/x/auth/tx/config" + "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/crisis" + genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli" + + cmtcfg "github.com/cometbft/cometbft/config" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing/simapp" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing/simapp/params" +) + +// NewRootCmd creates a new root command for simd. It is called once in the +// main function. +func NewRootCmd() *cobra.Command { + // we "pre"-instantiate the application for getting the injected/configured encoding configuration + // note, this is not necessary when using app wiring, as depinject can be directly used (see root_v2.go) + tempApp := simapp.NewSimApp(log.NewNopLogger(), dbm.NewMemDB(), nil, true, simtestutil.NewAppOptionsWithFlagHome(tempDir()), nil) + encodingConfig := params.EncodingConfig{ + InterfaceRegistry: tempApp.InterfaceRegistry(), + Codec: tempApp.AppCodec(), + TxConfig: tempApp.TxConfig(), + Amino: tempApp.LegacyAmino(), + } + + initClientCtx := client.Context{}. + WithCodec(encodingConfig.Codec). + WithInterfaceRegistry(encodingConfig.InterfaceRegistry). + WithLegacyAmino(encodingConfig.Amino). + WithInput(os.Stdin). + WithAccountRetriever(types.AccountRetriever{}). + WithHomeDir(simapp.DefaultNodeHome). + WithViper("") // In simapp, we don't use any prefix for env variables. + + rootCmd := &cobra.Command{ + Use: "simd", + Short: "simulation app", + SilenceErrors: true, + PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { + // set the default command outputs + cmd.SetOut(cmd.OutOrStdout()) + cmd.SetErr(cmd.ErrOrStderr()) + + initClientCtx = initClientCtx.WithCmdContext(cmd.Context()) + initClientCtx, err := client.ReadPersistentCommandFlags(initClientCtx, cmd.Flags()) + if err != nil { + return err + } + + initClientCtx, err = config.ReadFromClientConfig(initClientCtx) + if err != nil { + return err + } + + // This needs to go after ReadFromClientConfig, as that function + // sets the RPC client needed for SIGN_MODE_TEXTUAL. + enabledSignModes := append(tx.DefaultSignModes, signing.SignMode_SIGN_MODE_TEXTUAL) //nolint:gocritic // we know we aren't appending to the same slice + txConfigOpts := tx.ConfigOptions{ + EnabledSignModes: enabledSignModes, + TextualCoinMetadataQueryFn: txmodule.NewGRPCCoinMetadataQueryFn(initClientCtx), + } + txConfigWithTextual, err := tx.NewTxConfigWithOptions( + codec.NewProtoCodec(encodingConfig.InterfaceRegistry), + txConfigOpts, + ) + if err != nil { + return err + } + initClientCtx = initClientCtx.WithTxConfig(txConfigWithTextual) + + if err := client.SetCmdClientContextHandler(initClientCtx, cmd); err != nil { + return err + } + + customAppTemplate, customAppConfig := initAppConfig() + customCMTConfig := initCometBFTConfig() + + return server.InterceptConfigsPreRunHandler(cmd, customAppTemplate, customAppConfig, customCMTConfig) + }, + } + + initRootCmd(rootCmd, encodingConfig, tempApp.BasicModuleManager) + + autoCliOpts, err := enrichAutoCliOpts(tempApp.AutoCliOpts(), initClientCtx) + if err != nil { + panic(err) + } + + if err := autoCliOpts.EnhanceRootCommand(rootCmd); err != nil { + panic(err) + } + + return rootCmd +} + +func enrichAutoCliOpts(autoCliOpts autocli.AppOptions, clientCtx client.Context) (autocli.AppOptions, error) { + autoCliOpts.AddressCodec = addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32AccountAddrPrefix()) + autoCliOpts.ValidatorAddressCodec = addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32ValidatorAddrPrefix()) + autoCliOpts.ConsensusAddressCodec = addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32ConsensusAddrPrefix()) + + var err error + clientCtx, err = config.ReadFromClientConfig(clientCtx) + if err != nil { + return autocli.AppOptions{}, err + } + + autoCliOpts.ClientCtx = clientCtx + + autoCliOpts.Keyring, err = keyring.NewAutoCLIKeyring(clientCtx.Keyring) + if err != nil { + return autocli.AppOptions{}, err + } + + return autoCliOpts, nil +} + +// initCometBFTConfig helps to override default CometBFT Config values. +// return cmtcfg.DefaultConfig if no custom configuration is required for the application. +func initCometBFTConfig() *cmtcfg.Config { + cfg := cmtcfg.DefaultConfig() + + // these values put a higher strain on node memory + // cfg.P2P.MaxNumInboundPeers = 100 + // cfg.P2P.MaxNumOutboundPeers = 40 + + return cfg +} + +// initAppConfig helps to override default appConfig template and configs. +// return "", nil if no custom configuration is required for the application. +func initAppConfig() (string, interface{}) { + // The following code snippet is just for reference. + + // WASMConfig defines configuration for the wasm module. + type WASMConfig struct { + // This is the maximum sdk gas (wasm and storage) that we allow for any x/wasm "smart" queries + QueryGasLimit uint64 `mapstructure:"query_gas_limit"` + + // Address defines the gRPC-web server to listen on + LruSize uint64 `mapstructure:"lru_size"` + } + + type CustomAppConfig struct { + serverconfig.Config + + WASM WASMConfig `mapstructure:"wasm"` + } + + // Optionally allow the chain developer to overwrite the SDK's default + // server config. + srvCfg := serverconfig.DefaultConfig() + // The SDK's default minimum gas price is set to "" (empty value) inside + // app.toml. If left empty by validators, the node will halt on startup. + // However, the chain developer can set a default app.toml value for their + // validators here. + // + // In summary: + // - if you leave srvCfg.MinGasPrices = "", all validators MUST tweak their + // own app.toml config, + // - if you set srvCfg.MinGasPrices non-empty, validators CAN tweak their + // own app.toml to override, or use this default value. + // + // In simapp, we set the min gas prices to 0. + srvCfg.MinGasPrices = "0stake" + // srvCfg.BaseConfig.IAVLDisableFastNode = true // disable fastnode by default + + customAppConfig := CustomAppConfig{ + Config: *srvCfg, + WASM: WASMConfig{ + LruSize: 1, + QueryGasLimit: 300000, + }, + } + + customAppTemplate := serverconfig.DefaultConfigTemplate + ` +[wasm] +# This is the maximum sdk gas (wasm and storage) that we allow for any x/wasm "smart" queries +query_gas_limit = 300000 +# This is the number of wasm vm instances we keep cached in memory for speed-up +# Warning: this is currently unstable and may lead to crashes, best to keep for 0 unless testing locally +lru_size = 0` + + return customAppTemplate, customAppConfig +} + +func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig, basicManager module.BasicManager) { + cfg := sdk.GetConfig() + cfg.Seal() + + rootCmd.AddCommand( + genutilcli.InitCmd(basicManager, simapp.DefaultNodeHome), + sdkdebug.Cmd(), + confixcmd.ConfigCommand(), + pruning.Cmd(newApp, simapp.DefaultNodeHome), + snapshot.Cmd(newApp), + server.QueryBlockResultsCmd(), + ) + + server.AddCommands(rootCmd, simapp.DefaultNodeHome, newApp, appExport, addModuleInitFlags) + + // add keybase, auxiliary RPC, query, genesis, and tx child commands + rootCmd.AddCommand( + server.StatusCommand(), + genesisCommand(encodingConfig, basicManager), + txCommand(), + queryCommand(), + keys.Commands(), + ) +} + +func addModuleInitFlags(startCmd *cobra.Command) { + crisis.AddModuleInitFlags(startCmd) + preCheck := func(cmd *cobra.Command, _ []string) error { + return CheckLibwasmVersion(getExpectedLibwasmVersion()) + } + startCmd.PreRunE = chainPreRuns(preCheck, startCmd.PreRunE) +} + +func queryCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "query", + Aliases: []string{"q"}, + Short: "Querying subcommands", + DisableFlagParsing: false, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + cmd.AddCommand( + rpc.ValidatorCommand(), + server.QueryBlockCmd(), + authcmd.QueryTxsByEventsCmd(), + server.QueryBlocksCmd(), + authcmd.QueryTxCmd(), + authcmd.GetSimulateCmd(), + ) + + return cmd +} + +func txCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "tx", + Short: "Transactions subcommands", + DisableFlagParsing: false, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + cmd.AddCommand( + authcmd.GetSignCommand(), + authcmd.GetSignBatchCommand(), + authcmd.GetMultiSignCommand(), + authcmd.GetMultiSignBatchCmd(), + authcmd.GetValidateSignaturesCommand(), + authcmd.GetBroadcastCommand(), + authcmd.GetEncodeCommand(), + authcmd.GetDecodeCommand(), + authcmd.GetSimulateCmd(), + ) + + return cmd +} + +// genesisCommand builds genesis-related `simd genesis` command. Users may provide application specific commands as a parameter +func genesisCommand(encodingConfig params.EncodingConfig, basicManager module.BasicManager, cmds ...*cobra.Command) *cobra.Command { + cmd := genutilcli.Commands(encodingConfig.TxConfig, basicManager, simapp.DefaultNodeHome) + + for _, subCmd := range cmds { + cmd.AddCommand(subCmd) + } + return cmd +} + +// newApp creates the application +func newApp( + logger log.Logger, + db dbm.DB, + traceStore io.Writer, + appOpts servertypes.AppOptions, +) servertypes.Application { + baseappOptions := server.DefaultBaseappOptions(appOpts) + + return simapp.NewSimApp( + logger, db, traceStore, true, + appOpts, nil, + baseappOptions..., + ) +} + +// appExport creates a new simapp (optionally at a given height) and exports state. +func appExport( + logger log.Logger, + db dbm.DB, + traceStore io.Writer, + height int64, + forZeroHeight bool, + jailAllowedAddrs []string, + appOpts servertypes.AppOptions, + modulesToExport []string, +) (servertypes.ExportedApp, error) { + var simApp *simapp.SimApp + + // this check is necessary as we use the flag in x/upgrade. + // we can exit more gracefully by checking the flag here. + homePath, ok := appOpts.Get(flags.FlagHome).(string) + if !ok || homePath == "" { + return servertypes.ExportedApp{}, errors.New("application home not set") + } + + viperAppOpts, ok := appOpts.(*viper.Viper) + if !ok { + return servertypes.ExportedApp{}, errors.New("appOpts is not viper.Viper") + } + + // overwrite the FlagInvCheckPeriod + viperAppOpts.Set(server.FlagInvCheckPeriod, 1) + appOpts = viperAppOpts + + if height != -1 { + simApp = simapp.NewSimApp(logger, db, traceStore, false, appOpts, nil) + + if err := simApp.LoadHeight(height); err != nil { + return servertypes.ExportedApp{}, err + } + } else { + simApp = simapp.NewSimApp(logger, db, traceStore, true, appOpts, nil) + } + + return simApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs, modulesToExport) +} + +var tempDir = func() string { + dir, err := os.MkdirTemp("", "simapp") + if err != nil { + dir = simapp.DefaultNodeHome + } + defer os.RemoveAll(dir) + + return dir +} + +// CheckLibwasmVersion ensures that the libwasmvm version loaded at runtime matches the version +// of the github.com/CosmWasm/wasmvm dependency in go.mod. +// Ref: https://github.com/cosmos/ibc-go/issues/4821#issuecomment-1747240445 +func CheckLibwasmVersion(wasmExpectedVersion string) error { + if wasmExpectedVersion == "" { + return fmt.Errorf("wasmvm module not exist") + } + wasmVersion, err := wasmvm.LibwasmvmVersion() + if err != nil { + return fmt.Errorf("unable to retrieve libwasmversion %w", err) + } + if !strings.Contains(wasmExpectedVersion, wasmVersion) { + return fmt.Errorf("libwasmversion mismatch. got: %s; expected: %s", wasmVersion, wasmExpectedVersion) + } + return nil +} + +type preRunFn func(cmd *cobra.Command, args []string) error + +func chainPreRuns(pfns ...preRunFn) preRunFn { + return func(cmd *cobra.Command, args []string) error { + for _, pfn := range pfns { + if pfn != nil { + if err := pfn(cmd, args); err != nil { + return err + } + } + } + return nil + } +} + +func getExpectedLibwasmVersion() string { + buildInfo, ok := debug.ReadBuildInfo() + if !ok { + panic("can't read build info") + } + for _, d := range buildInfo.Deps { + if d.Path != "github.com/CosmWasm/wasmvm" { + continue + } + if d.Replace != nil { + return d.Replace.Version + } + return d.Version + } + return "" +} diff --git a/modules/light-clients/08-wasm/testing/simapp/simd/main.go b/modules/light-clients/08-wasm/testing/simapp/simd/main.go new file mode 100644 index 00000000000..6ef7da8fd40 --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/simd/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "os" + + "cosmossdk.io/log" + + svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing/simapp" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing/simapp/simd/cmd" +) + +func main() { + rootCmd := cmd.NewRootCmd() + if err := svrcmd.Execute(rootCmd, "", simapp.DefaultNodeHome); err != nil { + log.NewLogger(rootCmd.OutOrStderr()).Error("failure when running app", "err", err) + os.Exit(1) + } +} diff --git a/modules/light-clients/08-wasm/testing/simapp/test_helpers.go b/modules/light-clients/08-wasm/testing/simapp/test_helpers.go new file mode 100644 index 00000000000..d2c9e5cf678 --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/test_helpers.go @@ -0,0 +1,113 @@ +package simapp + +import ( + "encoding/json" + "path/filepath" + "testing" + + dbm "github.com/cosmos/cosmos-db" + "github.com/stretchr/testify/require" + + "cosmossdk.io/log" + sdkmath "cosmossdk.io/math" + "cosmossdk.io/store/snapshots" + snapshottypes "cosmossdk.io/store/snapshots/types" + + bam "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/cosmos/cosmos-sdk/server" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + abci "github.com/cometbft/cometbft/abci/types" + cmttypes "github.com/cometbft/cometbft/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" + "github.com/cosmos/ibc-go/v8/testing/mock" +) + +func setup(tb testing.TB, chainID string, withGenesis bool, invCheckPeriod uint, mockVM ibcwasm.WasmEngine) (*SimApp, GenesisState) { + tb.Helper() + + db := dbm.NewMemDB() + nodeHome := tb.TempDir() + snapshotDir := filepath.Join(nodeHome, "data", "snapshots") + + snapshotDB, err := dbm.NewDB("metadata", dbm.GoLevelDBBackend, snapshotDir) + require.NoError(tb, err) + tb.Cleanup(func() { snapshotDB.Close() }) + snapshotStore, err := snapshots.NewStore(snapshotDB, snapshotDir) + require.NoError(tb, err) + + appOptions := make(simtestutil.AppOptionsMap, 0) + appOptions[flags.FlagHome] = nodeHome // ensure unique folder + appOptions[server.FlagInvCheckPeriod] = invCheckPeriod + app := NewSimApp(log.NewNopLogger(), db, nil, true, appOptions, mockVM, bam.SetChainID(chainID), bam.SetSnapshot(snapshotStore, snapshottypes.SnapshotOptions{KeepRecent: 2})) + + if withGenesis { + return app, app.DefaultGenesis() + } + + return app, GenesisState{} +} + +// SetupWithEmptyStore set up a simapp instance with empty DB +func SetupWithEmptyStore(tb testing.TB, mockVM ibcwasm.WasmEngine) *SimApp { + tb.Helper() + + app, _ := setup(tb, "", false, 0, mockVM) + return app +} + +// SetupWithGenesisValSet initializes a new SimApp with a validator set and genesis accounts +// that also act as delegators. For simplicity, each validator is bonded with a delegation +// of one consensus engine unit in the default token of the simapp from first genesis +// account. A Nop logger is set in SimApp. +func SetupWithGenesisValSetSnapshotter(t *testing.T, mockVM ibcwasm.WasmEngine, valSet *cmttypes.ValidatorSet, genAccs []authtypes.GenesisAccount, balances ...banktypes.Balance) *SimApp { + t.Helper() + + app, genesisState := setup(t, "", true, 5, mockVM) + genesisState, err := simtestutil.GenesisStateWithValSet(app.AppCodec(), genesisState, valSet, genAccs, balances...) + require.NoError(t, err) + + stateBytes, err := json.MarshalIndent(genesisState, "", " ") + require.NoError(t, err) + + // init chain will set the validator set and initialize the genesis accounts + _, err = app.InitChain(&abci.RequestInitChain{ + Validators: []abci.ValidatorUpdate{}, + ConsensusParams: simtestutil.DefaultConsensusParams, + AppStateBytes: stateBytes, + }) + require.NoError(t, err) + + return app +} + +// SetupWithSnapshotter initializes a new SimApp with a configured snapshot db. A Nop logger is set in SimApp. +func SetupWithSnapshotter(t *testing.T, mockVM ibcwasm.WasmEngine) *SimApp { + t.Helper() + + privVal := mock.NewPV() + pubKey, err := privVal.GetPubKey() + require.NoError(t, err) + + // create validator set with single validator + validator := cmttypes.NewValidator(pubKey, 1) + valSet := cmttypes.NewValidatorSet([]*cmttypes.Validator{validator}) + + // generate genesis account + senderPrivKey := secp256k1.GenPrivKey() + acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0) + balance := banktypes.Balance{ + Address: acc.GetAddress().String(), + Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(100000000000000))), + } + + app := SetupWithGenesisValSetSnapshotter(t, mockVM, valSet, []authtypes.GenesisAccount{acc}, balance) + + return app +} diff --git a/modules/light-clients/08-wasm/testing/values.go b/modules/light-clients/08-wasm/testing/values.go new file mode 100644 index 00000000000..e9ec6d49538 --- /dev/null +++ b/modules/light-clients/08-wasm/testing/values.go @@ -0,0 +1,32 @@ +package testing + +import ( + "errors" + + "github.com/cosmos/cosmos-sdk/codec" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" +) + +var ( + // Represents the code of the wasm contract used in the tests with a mock vm. + Code = []byte("01234567012345670123456701234567") + contractClientState = []byte{1} + contractConsensusState = []byte{2} + MockClientStateBz = []byte("client-state-data") + MockConsensusStateBz = []byte("consensus-state-data") + MockValidProofBz = []byte("valid proof") + MockInvalidProofBz = []byte("invalid proof") + MockUpgradedClientStateProofBz = []byte("upgraded client state proof") + MockUpgradedConsensusStateProofBz = []byte("upgraded consensus state proof") + + ErrMockContract = errors.New("mock contract error") + ErrMockVM = errors.New("mock vm error") +) + +// CreateMockClientStateBz returns valid client state bytes for use in tests. +func CreateMockClientStateBz(cdc codec.BinaryCodec, checksum []byte) []byte { + mockClientSate := types.NewClientState([]byte{1}, checksum, clienttypes.NewHeight(2000, 2)) + return clienttypes.MustMarshalClientState(cdc, mockClientSate) +} diff --git a/modules/light-clients/08-wasm/testing/wasm_endpoint.go b/modules/light-clients/08-wasm/testing/wasm_endpoint.go new file mode 100644 index 00000000000..3e9cc09c697 --- /dev/null +++ b/modules/light-clients/08-wasm/testing/wasm_endpoint.go @@ -0,0 +1,49 @@ +package testing + +import ( + "crypto/sha256" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + ibctesting "github.com/cosmos/ibc-go/v8/testing" +) + +// WasmEndpoint is a wrapper around the ibctesting pkg Endpoint struct. +// It will override any functions which require special handling for the wasm client. +type WasmEndpoint struct { + *ibctesting.Endpoint +} + +// NewWasmEndpoint returns a wasm endpoint with the default ibctesting pkg +// Endpoint embedded. +func NewWasmEndpoint(chain *ibctesting.TestChain) *WasmEndpoint { + return &WasmEndpoint{ + Endpoint: ibctesting.NewDefaultEndpoint(chain), + } +} + +// CreateClient creates an wasm client on a mock cometbft chain. +// The client and consensus states are represented by byte slices +// and the starting height is 1. +func (endpoint *WasmEndpoint) CreateClient() error { + checksum := sha256.Sum256(Code) + clientState := types.NewClientState(contractClientState, checksum[:], clienttypes.NewHeight(0, 1)) + consensusState := types.NewConsensusState(contractConsensusState, 0) + + msg, err := clienttypes.NewMsgCreateClient( + clientState, consensusState, endpoint.Chain.SenderAccount.GetAddress().String(), + ) + require.NoError(endpoint.Chain.TB, err) + + res, err := endpoint.Chain.SendMsgs(msg) + if err != nil { + return err + } + + endpoint.ClientID, err = ibctesting.ParseClientIDFromEvents(res.Events) + require.NoError(endpoint.Chain.TB, err) + + return nil +} diff --git a/modules/light-clients/08-wasm/types/client_message.go b/modules/light-clients/08-wasm/types/client_message.go new file mode 100644 index 00000000000..483a9ed135c --- /dev/null +++ b/modules/light-clients/08-wasm/types/client_message.go @@ -0,0 +1,23 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/ibc-go/v8/modules/core/exported" +) + +var _ exported.ClientMessage = &ClientMessage{} + +// ClientType is a Wasm light client. +func (ClientMessage) ClientType() string { + return exported.Wasm +} + +// ValidateBasic defines a basic validation for the wasm client message. +func (c ClientMessage) ValidateBasic() error { + if len(c.Data) == 0 { + return errorsmod.Wrap(ErrInvalidData, "data cannot be empty") + } + + return nil +} diff --git a/modules/light-clients/08-wasm/types/client_message_test.go b/modules/light-clients/08-wasm/types/client_message_test.go new file mode 100644 index 00000000000..4fa2a6ffc51 --- /dev/null +++ b/modules/light-clients/08-wasm/types/client_message_test.go @@ -0,0 +1,51 @@ +package types_test + +import ( + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + "github.com/cosmos/ibc-go/v8/modules/core/exported" +) + +func (suite *TypesTestSuite) TestClientMessageValidateBasic() { + testCases := []struct { + name string + clientMessage *types.ClientMessage + expPass bool + }{ + { + "valid client message", + &types.ClientMessage{ + Data: []byte("data"), + }, + true, + }, + { + "data is nil", + &types.ClientMessage{ + Data: nil, + }, + false, + }, + { + "data is empty", + &types.ClientMessage{ + Data: []byte{}, + }, + false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + clientMessage := tc.clientMessage + + suite.Require().Equal(exported.Wasm, clientMessage.ClientType()) + err := clientMessage.ValidateBasic() + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} diff --git a/modules/light-clients/08-wasm/types/client_state.go b/modules/light-clients/08-wasm/types/client_state.go new file mode 100644 index 00000000000..140899976a7 --- /dev/null +++ b/modules/light-clients/08-wasm/types/client_state.go @@ -0,0 +1,217 @@ +package types + +import ( + "encoding/hex" + + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types" + ibcerrors "github.com/cosmos/ibc-go/v8/modules/core/errors" + "github.com/cosmos/ibc-go/v8/modules/core/exported" +) + +var _ exported.ClientState = (*ClientState)(nil) + +// NewClientState creates a new ClientState instance. +func NewClientState(data []byte, checksum []byte, height clienttypes.Height) *ClientState { + return &ClientState{ + Data: data, + Checksum: checksum, + LatestHeight: height, + } +} + +// ClientType is Wasm light client. +func (ClientState) ClientType() string { + return exported.Wasm +} + +// GetLatestHeight returns latest block height. +func (cs ClientState) GetLatestHeight() exported.Height { + return cs.LatestHeight +} + +// Validate performs a basic validation of the client state fields. +func (cs ClientState) Validate() error { + if len(cs.Data) == 0 { + return errorsmod.Wrap(ErrInvalidData, "data cannot be empty") + } + + if err := ValidateWasmChecksum(cs.Checksum); err != nil { + return err + } + + return nil +} + +// Status returns the status of the wasm client. +// The client may be: +// - Active: frozen height is zero and client is not expired +// - Frozen: frozen height is not zero +// - Expired: the latest consensus state timestamp + trusting period <= current time +// - Unauthorized: the client type is not registered as an allowed client type +// +// A frozen client will become expired, so the Frozen status +// has higher precedence. +func (cs ClientState) Status(ctx sdk.Context, clientStore storetypes.KVStore, _ codec.BinaryCodec) exported.Status { + // Return unauthorized if the checksum hasn't been previously stored via storeWasmCode. + if !HasChecksum(ctx, cs.Checksum) { + return exported.Unauthorized + } + + payload := QueryMsg{Status: &StatusMsg{}} + result, err := wasmQuery[StatusResult](ctx, clientStore, &cs, payload) + if err != nil { + return exported.Unknown + } + + return exported.Status(result.Status) +} + +// 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 { + return &cs +} + +// GetTimestampAtHeight returns the timestamp in nanoseconds of the consensus state at the given height. +func (cs ClientState) GetTimestampAtHeight( + ctx sdk.Context, + clientStore storetypes.KVStore, + cdc codec.BinaryCodec, + height exported.Height, +) (uint64, error) { + timestampHeight, ok := height.(clienttypes.Height) + if !ok { + return 0, errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", clienttypes.Height{}, height) + } + + payload := QueryMsg{ + TimestampAtHeight: &TimestampAtHeightMsg{ + Height: timestampHeight, + }, + } + + result, err := wasmQuery[TimestampAtHeightResult](ctx, clientStore, &cs, payload) + if err != nil { + return 0, errorsmod.Wrapf(err, "height (%s)", height) + } + + return result.Timestamp, nil +} + +// Initialize checks that the initial consensus state is an 08-wasm consensus state and +// sets the client state, consensus state in the provided client store. +// It also initializes the wasm contract for the client. +func (cs ClientState) Initialize(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, state exported.ConsensusState) error { + consensusState, ok := state.(*ConsensusState) + if !ok { + return errorsmod.Wrapf(clienttypes.ErrInvalidConsensus, "invalid initial consensus state. expected type: %T, got: %T", + &ConsensusState{}, state) + } + + // Do not allow initialization of a client with a checksum that hasn't been previously stored via storeWasmCode. + if !HasChecksum(ctx, cs.Checksum) { + return errorsmod.Wrapf(ErrInvalidChecksum, "checksum (%s) has not been previously stored", hex.EncodeToString(cs.Checksum)) + } + + payload := InstantiateMessage{ + ClientState: &cs, + ConsensusState: consensusState, + } + + return wasmInstantiate(ctx, cdc, clientStore, &cs, payload) +} + +// VerifyMembership is a generic proof verification method which verifies a proof of the existence of a value at a given CommitmentPath at the specified height. +// The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). +// If a zero proof height is passed in, it will fail to retrieve the associated consensus state. +func (cs ClientState) VerifyMembership( + ctx sdk.Context, + clientStore storetypes.KVStore, + cdc codec.BinaryCodec, + height exported.Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path exported.Path, + value []byte, +) error { + proofHeight, ok := height.(clienttypes.Height) + if !ok { + return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", clienttypes.Height{}, height) + } + + if cs.GetLatestHeight().LT(height) { + return errorsmod.Wrapf( + ibcerrors.ErrInvalidHeight, + "client state height < proof height (%d < %d), please ensure the client has been updated", cs.GetLatestHeight(), height, + ) + } + + merklePath, ok := path.(commitmenttypes.MerklePath) + if !ok { + return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", commitmenttypes.MerklePath{}, path) + } + + payload := SudoMsg{ + VerifyMembership: &VerifyMembershipMsg{ + Height: proofHeight, + DelayTimePeriod: delayTimePeriod, + DelayBlockPeriod: delayBlockPeriod, + Proof: proof, + Path: merklePath, + Value: value, + }, + } + _, err := wasmSudo[EmptyResult](ctx, cdc, payload, clientStore, &cs) + return err +} + +// VerifyNonMembership is a generic proof verification method which verifies the absence of a given CommitmentPath at a specified height. +// The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). +// If a zero proof height is passed in, it will fail to retrieve the associated consensus state. +func (cs ClientState) VerifyNonMembership( + ctx sdk.Context, + clientStore storetypes.KVStore, + cdc codec.BinaryCodec, + height exported.Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path exported.Path, +) error { + proofHeight, ok := height.(clienttypes.Height) + if !ok { + return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", clienttypes.Height{}, height) + } + + if cs.GetLatestHeight().LT(height) { + return errorsmod.Wrapf( + ibcerrors.ErrInvalidHeight, + "client state height < proof height (%d < %d), please ensure the client has been updated", cs.GetLatestHeight(), height, + ) + } + + merklePath, ok := path.(commitmenttypes.MerklePath) + if !ok { + return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", commitmenttypes.MerklePath{}, path) + } + + payload := SudoMsg{ + VerifyNonMembership: &VerifyNonMembershipMsg{ + Height: proofHeight, + DelayTimePeriod: delayTimePeriod, + DelayBlockPeriod: delayBlockPeriod, + Proof: proof, + Path: merklePath, + }, + } + _, err := wasmSudo[EmptyResult](ctx, cdc, payload, clientStore, &cs) + return err +} diff --git a/modules/light-clients/08-wasm/types/client_state_test.go b/modules/light-clients/08-wasm/types/client_state_test.go new file mode 100644 index 00000000000..440ea360e91 --- /dev/null +++ b/modules/light-clients/08-wasm/types/client_state_test.go @@ -0,0 +1,618 @@ +package types_test + +import ( + "crypto/sha256" + "encoding/json" + "time" + + wasmvm "github.com/CosmWasm/wasmvm" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v8/modules/core/errors" + "github.com/cosmos/ibc-go/v8/modules/core/exported" + solomachine "github.com/cosmos/ibc-go/v8/modules/light-clients/06-solomachine" + ibctesting "github.com/cosmos/ibc-go/v8/testing" + ibcmock "github.com/cosmos/ibc-go/v8/testing/mock" +) + +func (suite *TypesTestSuite) TestStatus() { + testCases := []struct { + name string + malleate func() + expStatus exported.Status + }{ + { + "client is active", + func() {}, + exported.Active, + }, + { + "client is frozen", + func() { + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(checksum wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) ([]byte, uint64, error) { + resp, err := json.Marshal(types.StatusResult{Status: exported.Frozen.String()}) + suite.Require().NoError(err) + return resp, wasmtesting.DefaultGasUsed, nil + }) + }, + exported.Frozen, + }, + { + "client status is expired", + func() { + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(checksum wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) ([]byte, uint64, error) { + resp, err := json.Marshal(types.StatusResult{Status: exported.Expired.String()}) + suite.Require().NoError(err) + return resp, wasmtesting.DefaultGasUsed, nil + }) + }, + exported.Expired, + }, + { + "client status is unknown: vm returns an error", + func() { + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(checksum wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) ([]byte, uint64, error) { + return nil, 0, wasmtesting.ErrMockContract + }) + }, + exported.Unknown, + }, + { + "client status is unauthorized: checksum is not stored", + func() { + err := ibcwasm.Checksums.Remove(suite.chainA.GetContext(), suite.checksum) + suite.Require().NoError(err) + }, + exported.Unauthorized, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + + tc.malleate() + + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) + clientState := endpoint.GetClientState() + + status := clientState.Status(suite.chainA.GetContext(), clientStore, suite.chainA.App.AppCodec()) + suite.Require().Equal(tc.expStatus, status) + }) + } +} + +func (suite *TypesTestSuite) TestGetTimestampAtHeight() { + var height exported.Height + + expectedTimestamp := uint64(time.Now().UnixNano()) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", + func() { + suite.mockVM.RegisterQueryCallback(types.TimestampAtHeightMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, queryMsg []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) ([]byte, uint64, error) { + var payload types.QueryMsg + err := json.Unmarshal(queryMsg, &payload) + suite.Require().NoError(err) + + suite.Require().NotNil(payload.TimestampAtHeight) + suite.Require().Nil(payload.CheckForMisbehaviour) + suite.Require().Nil(payload.Status) + suite.Require().Nil(payload.ExportMetadata) + suite.Require().Nil(payload.VerifyClientMessage) + + resp, err := json.Marshal(types.TimestampAtHeightResult{Timestamp: expectedTimestamp}) + suite.Require().NoError(err) + + return resp, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + }, + { + "failure: contract returns error", + func() { + suite.mockVM.RegisterQueryCallback(types.TimestampAtHeightMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) ([]byte, uint64, error) { + return nil, 0, wasmtesting.ErrMockContract + }) + }, + types.ErrWasmContractCallFailed, + }, + { + "error: invalid height", + func() { + height = ibcmock.Height{} + }, + ibcerrors.ErrInvalidType, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) + clientState := endpoint.GetClientState().(*types.ClientState) + height = clientState.GetLatestHeight() + + tc.malleate() + + timestamp, err := clientState.GetTimestampAtHeight(suite.chainA.GetContext(), clientStore, suite.chainA.App.AppCodec(), height) + + expPass := tc.expErr == nil + if expPass { + suite.Require().NoError(err) + suite.Require().Equal(expectedTimestamp, timestamp) + } else { + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *TypesTestSuite) TestValidate() { + testCases := []struct { + name string + clientState *types.ClientState + expPass bool + }{ + { + name: "valid client", + clientState: types.NewClientState([]byte{0}, wasmtesting.Code, clienttypes.ZeroHeight()), + expPass: true, + }, + { + name: "nil data", + clientState: types.NewClientState(nil, wasmtesting.Code, clienttypes.ZeroHeight()), + expPass: false, + }, + { + name: "empty data", + clientState: types.NewClientState([]byte{}, wasmtesting.Code, clienttypes.ZeroHeight()), + expPass: false, + }, + { + name: "nil checksum", + clientState: types.NewClientState([]byte{0}, nil, clienttypes.ZeroHeight()), + expPass: false, + }, + { + name: "empty checksum", + clientState: types.NewClientState([]byte{0}, []byte{}, clienttypes.ZeroHeight()), + expPass: false, + }, + { + name: "longer than 32 bytes checksum", + clientState: types.NewClientState( + []byte{0}, + []byte{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, + }, + clienttypes.ZeroHeight(), + ), + expPass: false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := tc.clientState.Validate() + if tc.expPass { + suite.Require().NoError(err, tc.name) + } else { + suite.Require().Error(err, tc.name) + } + }) + } +} + +func (suite *TypesTestSuite) TestInitialize() { + var ( + consensusState exported.ConsensusState + clientState exported.ClientState + clientStore storetypes.KVStore + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success: new mock client", + func() {}, + nil, + }, + { + "success: validate contract address", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, env wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + var payload types.InstantiateMessage + err := json.Unmarshal(initMsg, &payload) + suite.Require().NoError(err) + + suite.Require().Equal(env.Contract.Address, defaultWasmClientID) + + store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), payload.ClientState)) + store.Set(host.ConsensusStateKey(payload.ClientState.LatestHeight), clienttypes.MustMarshalConsensusState(suite.chainA.App.AppCodec(), payload.ConsensusState)) + + resp, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.Response{Data: resp}, 0, nil + } + }, + nil, + }, + { + "failure: clientStore prefix does not include clientID", + func() { + clientStore = suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), ibctesting.InvalidID) + }, + types.ErrWasmContractCallFailed, + }, + { + "failure: invalid consensus state", + func() { + // set upgraded consensus state to solomachine consensus state + consensusState = &solomachine.ConsensusState{} + }, + clienttypes.ErrInvalidConsensus, + }, + { + "failure: checksum has not been stored.", + func() { + clientState = types.NewClientState([]byte{1}, []byte("unknown"), clienttypes.NewHeight(0, 1)) + }, + types.ErrInvalidChecksum, + }, + { + "failure: InstantiateFn returns error", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + return nil, 0, wasmtesting.ErrMockContract + } + }, + types.ErrWasmContractCallFailed, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + checksum := sha256.Sum256(wasmtesting.Code) + clientState = types.NewClientState([]byte{1}, checksum[:], clienttypes.NewHeight(0, 1)) + consensusState = types.NewConsensusState([]byte{2}, 0) + + clientID := suite.chainA.App.GetIBCKeeper().ClientKeeper.GenerateClientIdentifier(suite.chainA.GetContext(), clientState.ClientType()) + clientStore = suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), clientID) + + tc.malleate() + + err := clientState.Initialize(suite.chainA.GetContext(), suite.chainA.Codec, clientStore, consensusState) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + + expClientState := clienttypes.MustMarshalClientState(suite.chainA.Codec, clientState) + suite.Require().Equal(expClientState, clientStore.Get(host.ClientStateKey())) + + expConsensusState := clienttypes.MustMarshalConsensusState(suite.chainA.Codec, consensusState) + suite.Require().Equal(expConsensusState, clientStore.Get(host.ConsensusStateKey(clientState.GetLatestHeight()))) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} + +func (suite *TypesTestSuite) TestVerifyMembership() { + var ( + clientState exported.ClientState + expClientStateBz []byte + path exported.Path + proof []byte + proofHeight exported.Height + value []byte + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + expClientStateBz = GetSimApp(suite.chainA).GetIBCKeeper().ClientKeeper.MustMarshalClientState(clientState) + suite.mockVM.RegisterSudoCallback(types.VerifyMembershipMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, _ wasmvm.KVStore, + _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction, + ) (*wasmvmtypes.Response, uint64, error) { + var payload types.SudoMsg + err := json.Unmarshal(sudoMsg, &payload) + suite.Require().NoError(err) + + suite.Require().NotNil(payload.VerifyMembership) + suite.Require().Nil(payload.UpdateState) + suite.Require().Nil(payload.UpdateStateOnMisbehaviour) + suite.Require().Nil(payload.VerifyNonMembership) + suite.Require().Nil(payload.VerifyUpgradeAndUpdateState) + suite.Require().Equal(proofHeight, payload.VerifyMembership.Height) + suite.Require().Equal(path, payload.VerifyMembership.Path) + suite.Require().Equal(proof, payload.VerifyMembership.Proof) + suite.Require().Equal(value, payload.VerifyMembership.Value) + + bz, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.Response{Data: bz}, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + }, + { + "success: with update client state", + func() { + suite.mockVM.RegisterSudoCallback(types.VerifyMembershipMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, + _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction, + ) (*wasmvmtypes.Response, uint64, error) { + var payload types.SudoMsg + err := json.Unmarshal(sudoMsg, &payload) + suite.Require().NoError(err) + + suite.Require().NotNil(payload.VerifyMembership) + suite.Require().Nil(payload.UpdateState) + suite.Require().Nil(payload.UpdateStateOnMisbehaviour) + suite.Require().Nil(payload.VerifyNonMembership) + suite.Require().Nil(payload.VerifyUpgradeAndUpdateState) + suite.Require().Equal(proofHeight, payload.VerifyMembership.Height) + suite.Require().Equal(path, payload.VerifyMembership.Path) + suite.Require().Equal(proof, payload.VerifyMembership.Proof) + suite.Require().Equal(value, payload.VerifyMembership.Value) + + bz, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + expClientStateBz = wasmtesting.CreateMockClientStateBz(suite.chainA.Codec, suite.checksum) + store.Set(host.ClientStateKey(), expClientStateBz) + + return &wasmvmtypes.Response{Data: bz}, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + }, + { + "wasm vm returns invalid proof error", + func() { + proof = wasmtesting.MockInvalidProofBz + + suite.mockVM.RegisterSudoCallback(types.VerifyMembershipMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, + _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction, + ) (*wasmvmtypes.Response, uint64, error) { + return nil, wasmtesting.DefaultGasUsed, commitmenttypes.ErrInvalidProof + }) + }, + types.ErrWasmContractCallFailed, + }, + { + "proof height greater than client state latest height", + func() { + proofHeight = clienttypes.NewHeight(1, 100) + }, + ibcerrors.ErrInvalidHeight, + }, + { + "invalid path argument", + func() { + path = ibcmock.KeyPath{} + }, + ibcerrors.ErrInvalidType, + }, + { + "proof height is invalid type", + func() { + proofHeight = ibcmock.Height{} + }, + ibcerrors.ErrInvalidType, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + + path = commitmenttypes.NewMerklePath("/ibc/key/path") + proof = wasmtesting.MockValidProofBz + proofHeight = clienttypes.NewHeight(0, 1) + value = []byte("value") + + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) + clientState = endpoint.GetClientState() + + tc.malleate() + + err = clientState.VerifyMembership(suite.chainA.GetContext(), clientStore, suite.chainA.Codec, proofHeight, 0, 0, proof, path, value) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + + clientStateBz := clientStore.Get(host.ClientStateKey()) + suite.Require().Equal(expClientStateBz, clientStateBz) + } else { + suite.Require().ErrorIs(err, tc.expError, "unexpected error in VerifyMembership") + } + }) + } +} + +func (suite *TypesTestSuite) TestVerifyNonMembership() { + var ( + clientState exported.ClientState + expClientStateBz []byte + path exported.Path + proof []byte + proofHeight exported.Height + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + expClientStateBz = GetSimApp(suite.chainA).GetIBCKeeper().ClientKeeper.MustMarshalClientState(clientState) + suite.mockVM.RegisterSudoCallback(types.VerifyNonMembershipMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, _ wasmvm.KVStore, + _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction, + ) (*wasmvmtypes.Response, uint64, error) { + var payload types.SudoMsg + err := json.Unmarshal(sudoMsg, &payload) + suite.Require().NoError(err) + + suite.Require().NotNil(payload.VerifyNonMembership) + suite.Require().Nil(payload.UpdateState) + suite.Require().Nil(payload.UpdateStateOnMisbehaviour) + suite.Require().Nil(payload.VerifyMembership) + suite.Require().Nil(payload.VerifyUpgradeAndUpdateState) + suite.Require().Equal(proofHeight, payload.VerifyNonMembership.Height) + suite.Require().Equal(path, payload.VerifyNonMembership.Path) + suite.Require().Equal(proof, payload.VerifyNonMembership.Proof) + + bz, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.Response{Data: bz}, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + }, + { + "success: with update client state", + func() { + suite.mockVM.RegisterSudoCallback(types.VerifyNonMembershipMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, + _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction, + ) (*wasmvmtypes.Response, uint64, error) { + var payload types.SudoMsg + err := json.Unmarshal(sudoMsg, &payload) + suite.Require().NoError(err) + + suite.Require().NotNil(payload.VerifyNonMembership) + suite.Require().Nil(payload.UpdateState) + suite.Require().Nil(payload.UpdateStateOnMisbehaviour) + suite.Require().Nil(payload.VerifyMembership) + suite.Require().Nil(payload.VerifyUpgradeAndUpdateState) + suite.Require().Equal(proofHeight, payload.VerifyNonMembership.Height) + suite.Require().Equal(path, payload.VerifyNonMembership.Path) + suite.Require().Equal(proof, payload.VerifyNonMembership.Proof) + + bz, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + expClientStateBz = wasmtesting.CreateMockClientStateBz(suite.chainA.Codec, suite.checksum) + store.Set(host.ClientStateKey(), expClientStateBz) + + return &wasmvmtypes.Response{Data: bz}, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + }, + { + "wasm vm returns invalid proof error", + func() { + proof = wasmtesting.MockInvalidProofBz + + suite.mockVM.RegisterSudoCallback(types.VerifyNonMembershipMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, + _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction, + ) (*wasmvmtypes.Response, uint64, error) { + return nil, wasmtesting.DefaultGasUsed, commitmenttypes.ErrInvalidProof + }) + }, + types.ErrWasmContractCallFailed, + }, + { + "proof height greater than client state latest height", + func() { + proofHeight = clienttypes.NewHeight(1, 100) + }, + ibcerrors.ErrInvalidHeight, + }, + { + "invalid path argument", + func() { + path = ibcmock.KeyPath{} + }, + ibcerrors.ErrInvalidType, + }, + { + "proof height is invalid type", + func() { + proofHeight = ibcmock.Height{} + }, + ibcerrors.ErrInvalidType, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + + path = commitmenttypes.NewMerklePath("/ibc/key/path") + proof = wasmtesting.MockInvalidProofBz + proofHeight = clienttypes.NewHeight(0, 1) + + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) + clientState = endpoint.GetClientState() + + tc.malleate() + + err = clientState.VerifyNonMembership(suite.chainA.GetContext(), clientStore, suite.chainA.Codec, proofHeight, 0, 0, proof, path) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + + clientStateBz := clientStore.Get(host.ClientStateKey()) + suite.Require().Equal(expClientStateBz, clientStateBz) + } else { + suite.Require().ErrorIs(err, tc.expError, "unexpected error in VerifyNonMembership") + } + }) + } +} diff --git a/modules/light-clients/08-wasm/types/codec.go b/modules/light-clients/08-wasm/types/codec.go new file mode 100644 index 00000000000..275809d98f7 --- /dev/null +++ b/modules/light-clients/08-wasm/types/codec.go @@ -0,0 +1,40 @@ +package types + +import ( + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/msgservice" + + "github.com/cosmos/ibc-go/v8/modules/core/exported" +) + +// RegisterInterfaces registers the Wasm concrete client-related +// implementations and interfaces. +func RegisterInterfaces(registry codectypes.InterfaceRegistry) { + registry.RegisterImplementations( + (*exported.ClientState)(nil), + &ClientState{}, + ) + registry.RegisterImplementations( + (*exported.ConsensusState)(nil), + &ConsensusState{}, + ) + registry.RegisterImplementations( + (*exported.ClientMessage)(nil), + &ClientMessage{}, + ) + registry.RegisterImplementations( + (*sdk.Msg)(nil), + &MsgStoreCode{}, + ) + registry.RegisterImplementations( + (*sdk.Msg)(nil), + &MsgMigrateContract{}, + ) + registry.RegisterImplementations( + (*sdk.Msg)(nil), + &MsgRemoveChecksum{}, + ) + + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) +} diff --git a/modules/light-clients/08-wasm/types/codec_test.go b/modules/light-clients/08-wasm/types/codec_test.go new file mode 100644 index 00000000000..91cf3846e16 --- /dev/null +++ b/modules/light-clients/08-wasm/types/codec_test.go @@ -0,0 +1,74 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + + wasm "github.com/cosmos/ibc-go/modules/light-clients/08-wasm" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" +) + +func TestCodecTypeRegistration(t *testing.T) { + testCases := []struct { + name string + typeURL string + expPass bool + }{ + { + "success: ClientState", + sdk.MsgTypeURL(&types.ClientState{}), + true, + }, + { + "success: ConsensusState", + sdk.MsgTypeURL(&types.ConsensusState{}), + true, + }, + { + "success: ClientMessage", + sdk.MsgTypeURL(&types.ClientMessage{}), + true, + }, + { + "success: MsgStoreCode", + sdk.MsgTypeURL(&types.MsgStoreCode{}), + true, + }, + { + "success: MsgMigrateContract", + sdk.MsgTypeURL(&types.MsgMigrateContract{}), + true, + }, + { + "success: MsgRemoveChecksum", + sdk.MsgTypeURL(&types.MsgRemoveChecksum{}), + true, + }, + { + "type not registered on codec", + "ibc.invalid.MsgTypeURL", + false, + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + encodingCfg := moduletestutil.MakeTestEncodingConfig(wasm.AppModuleBasic{}) + msg, err := encodingCfg.Codec.InterfaceRegistry().Resolve(tc.typeURL) + + if tc.expPass { + require.NotNil(t, msg) + require.NoError(t, err) + } else { + require.Nil(t, msg) + require.Error(t, err) + } + }) + } +} diff --git a/modules/light-clients/08-wasm/types/config.go b/modules/light-clients/08-wasm/types/config.go new file mode 100644 index 00000000000..7e947b3471e --- /dev/null +++ b/modules/light-clients/08-wasm/types/config.go @@ -0,0 +1,39 @@ +package types + +import "path/filepath" + +const ( + // ContractMemoryLimit is the memory limit of each contract execution (in MiB) + // constant value so all nodes run with the same limit. + ContractMemoryLimit = 32 + // MemoryCacheSize is the size of the wasm vm cache (in MiB), it is set to 0 to reduce unnecessary memory usage. + // See: https://github.com/CosmWasm/cosmwasm/pull/1925 + MemoryCacheSize = 0 + + defaultDataDir string = "ibc_08-wasm_client_data" + defaultSupportedCapabilities string = "iterator" + defaultContractDebugMode = false +) + +type WasmConfig struct { + // DataDir is the directory for Wasm blobs and various caches + DataDir string + // SupportedCapabilities is a comma separated list of capabilities supported by the chain + // See https://github.com/CosmWasm/wasmd/blob/e5049ba686ab71164a01f6e71e54347710a1f740/app/wasm.go#L3-L15 + // for more information. + SupportedCapabilities string + // ContractDebugMode is a flag to log what contracts print. It must be false on all + // production nodes, and only enabled in test environments or debug non-validating nodes. + ContractDebugMode bool +} + +// DefaultWasmConfig returns the default settings for WasmConfig. +// The homePath is the path to the directory where the data directory for +// Wasm blobs and caches will be stored. +func DefaultWasmConfig(homePath string) WasmConfig { + return WasmConfig{ + DataDir: filepath.Join(homePath, defaultDataDir), + SupportedCapabilities: defaultSupportedCapabilities, + ContractDebugMode: defaultContractDebugMode, + } +} diff --git a/modules/light-clients/08-wasm/types/consensus_state.go b/modules/light-clients/08-wasm/types/consensus_state.go new file mode 100644 index 00000000000..a299b64e791 --- /dev/null +++ b/modules/light-clients/08-wasm/types/consensus_state.go @@ -0,0 +1,35 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/ibc-go/v8/modules/core/exported" +) + +var _ exported.ConsensusState = (*ConsensusState)(nil) + +// NewConsensusState creates a new ConsensusState instance. +func NewConsensusState(data []byte, timestamp uint64) *ConsensusState { + return &ConsensusState{ + Data: data, + } +} + +// ClientType returns Wasm type. +func (ConsensusState) ClientType() string { + return exported.Wasm +} + +// GetTimestamp returns block time in nanoseconds of the header that created consensus state. +func (ConsensusState) GetTimestamp() uint64 { + return 0 +} + +// ValidateBasic defines a basic validation for the wasm client consensus state. +func (cs ConsensusState) ValidateBasic() error { + if len(cs.Data) == 0 { + return errorsmod.Wrap(ErrInvalidData, "data cannot be empty") + } + + return nil +} diff --git a/modules/light-clients/08-wasm/types/consensus_state_test.go b/modules/light-clients/08-wasm/types/consensus_state_test.go new file mode 100644 index 00000000000..9a2f012a3e5 --- /dev/null +++ b/modules/light-clients/08-wasm/types/consensus_state_test.go @@ -0,0 +1,46 @@ +package types_test + +import ( + "time" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + "github.com/cosmos/ibc-go/v8/modules/core/exported" +) + +func (suite *TypesTestSuite) TestConsensusStateValidateBasic() { + testCases := []struct { + name string + consensusState *types.ConsensusState + expectPass bool + }{ + { + "success", + types.NewConsensusState([]byte("data"), uint64(time.Now().Unix())), + true, + }, + { + "data is nil", + types.NewConsensusState(nil, uint64(time.Now().Unix())), + false, + }, + { + "data is empty", + types.NewConsensusState([]byte{}, uint64(time.Now().Unix())), + false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + // check just to increase coverage + suite.Require().Equal(exported.Wasm, tc.consensusState.ClientType()) + + err := tc.consensusState.ValidateBasic() + if tc.expectPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} diff --git a/modules/light-clients/08-wasm/types/contract_api.go b/modules/light-clients/08-wasm/types/contract_api.go new file mode 100644 index 00000000000..888eacd860e --- /dev/null +++ b/modules/light-clients/08-wasm/types/contract_api.go @@ -0,0 +1,133 @@ +package types + +import ( + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types" +) + +// InstantiateMessage is the message that is sent to the contract's instantiate entry point. +type InstantiateMessage struct { + ClientState *ClientState `json:"client_state"` + ConsensusState *ConsensusState `json:"consensus_state"` +} + +// QueryMsg is used to encode messages that are sent to the contract's query entry point. +// The json omitempty tag is mandatory since it omits any empty (default initialized) fields from the encoded JSON, +// this is required in order to be compatible with Rust's enum matching as used in the contract. +// Only one field should be set at a time. +type QueryMsg struct { + Status *StatusMsg `json:"status,omitempty"` + ExportMetadata *ExportMetadataMsg `json:"export_metadata,omitempty"` + TimestampAtHeight *TimestampAtHeightMsg `json:"timestamp_at_height,omitempty"` + VerifyClientMessage *VerifyClientMessageMsg `json:"verify_client_message,omitempty"` + CheckForMisbehaviour *CheckForMisbehaviourMsg `json:"check_for_misbehaviour,omitempty"` +} + +// StatusMsg is a queryMsg sent to the contract to query the status of the wasm client. +type StatusMsg struct{} + +// ExportMetadataMsg is a queryMsg sent to the contract to query the exported metadata of the wasm client. +type ExportMetadataMsg struct{} + +// TimestampAtHeightMsg is a queryMsg sent to the contract to query the timestamp at a given height. +type TimestampAtHeightMsg struct { + Height clienttypes.Height `json:"height"` +} + +// VerifyClientMessageMsg is a queryMsg sent to the contract to verify a client message. +type VerifyClientMessageMsg struct { + ClientMessage *ClientMessage `json:"client_message"` +} + +// CheckForMisbehaviourMsg is a queryMsg sent to the contract to check for misbehaviour. +type CheckForMisbehaviourMsg struct { + ClientMessage *ClientMessage `json:"client_message"` +} + +// SudoMsg is used to encode messages that are sent to the contract's sudo entry point. +// The json omitempty tag is mandatory since it omits any empty (default initialized) fields from the encoded JSON, +// this is required in order to be compatible with Rust's enum matching as used in the contract. +// Only one field should be set at a time. +type SudoMsg struct { + UpdateState *UpdateStateMsg `json:"update_state,omitempty"` + UpdateStateOnMisbehaviour *UpdateStateOnMisbehaviourMsg `json:"update_state_on_misbehaviour,omitempty"` + VerifyUpgradeAndUpdateState *VerifyUpgradeAndUpdateStateMsg `json:"verify_upgrade_and_update_state,omitempty"` + VerifyMembership *VerifyMembershipMsg `json:"verify_membership,omitempty"` + VerifyNonMembership *VerifyNonMembershipMsg `json:"verify_non_membership,omitempty"` + MigrateClientStore *MigrateClientStoreMsg `json:"migrate_client_store,omitempty"` +} + +// UpdateStateMsg is a sudoMsg sent to the contract to update the client state. +type UpdateStateMsg struct { + ClientMessage *ClientMessage `json:"client_message"` +} + +// UpdateStateOnMisbehaviourMsg is a sudoMsg sent to the contract to update its state on misbehaviour. +type UpdateStateOnMisbehaviourMsg struct { + ClientMessage *ClientMessage `json:"client_message"` +} + +// VerifyMembershipMsg is a sudoMsg sent to the contract to verify a membership proof. +type VerifyMembershipMsg struct { + Height clienttypes.Height `json:"height"` + DelayTimePeriod uint64 `json:"delay_time_period"` + DelayBlockPeriod uint64 `json:"delay_block_period"` + Proof []byte `json:"proof"` + Path commitmenttypes.MerklePath `json:"path"` + Value []byte `json:"value"` +} + +// VerifyNonMembershipMsg is a sudoMsg sent to the contract to verify a non-membership proof. +type VerifyNonMembershipMsg struct { + Height clienttypes.Height `json:"height"` + DelayTimePeriod uint64 `json:"delay_time_period"` + DelayBlockPeriod uint64 `json:"delay_block_period"` + Proof []byte `json:"proof"` + Path commitmenttypes.MerklePath `json:"path"` +} + +// VerifyUpgradeAndUpdateStateMsg is a sudoMsg sent to the contract to verify an upgrade and update its state. +type VerifyUpgradeAndUpdateStateMsg struct { + UpgradeClientState ClientState `json:"upgrade_client_state"` + UpgradeConsensusState ConsensusState `json:"upgrade_consensus_state"` + ProofUpgradeClient []byte `json:"proof_upgrade_client"` + ProofUpgradeConsensusState []byte `json:"proof_upgrade_consensus_state"` +} + +// MigrateClientStore is a sudoMsg sent to the contract to verify a given substitute client and update to its state. +type MigrateClientStoreMsg struct{} + +// ContractResult is a type constraint that defines the expected results that can be returned by a contract call/query. +type ContractResult interface { + EmptyResult | StatusResult | ExportMetadataResult | TimestampAtHeightResult | CheckForMisbehaviourResult | UpdateStateResult +} + +// EmptyResult is the default return type of any contract call that does not require a custom return type. +type EmptyResult struct{} + +// StatusResult is the expected return type of the statusMsg query. It returns the status of the wasm client. +type StatusResult struct { + Status string `json:"status"` +} + +// ExportMetadataResult is the expected return type of the exportMetadataMsg query. It returns the exported metadata of the wasm client. +type ExportMetadataResult struct { + GenesisMetadata []clienttypes.GenesisMetadata `json:"genesis_metadata"` +} + +// TimestampAtHeightResult is the expected return type of the timestampAtHeightMsg query. It returns the timestamp for a light client +// at a given height. +type TimestampAtHeightResult struct { + Timestamp uint64 `json:"timestamp"` +} + +// CheckForMisbehaviourResult is the expected return type of the checkForMisbehaviourMsg query. It returns a boolean indicating +// if misbehaviour was detected. +type CheckForMisbehaviourResult struct { + FoundMisbehaviour bool `json:"found_misbehaviour"` +} + +// UpdateStateResult is the expected return type of the updateStateMsg sudo call. It returns the updated consensus heights. +type UpdateStateResult struct { + Heights []clienttypes.Height `json:"heights"` +} diff --git a/modules/light-clients/08-wasm/types/errors.go b/modules/light-clients/08-wasm/types/errors.go new file mode 100644 index 00000000000..570e045e4fb --- /dev/null +++ b/modules/light-clients/08-wasm/types/errors.go @@ -0,0 +1,22 @@ +package types + +import errorsmod "cosmossdk.io/errors" + +var ( + ErrInvalid = errorsmod.Register(ModuleName, 2, "invalid") + ErrInvalidData = errorsmod.Register(ModuleName, 3, "invalid data") + ErrInvalidChecksum = errorsmod.Register(ModuleName, 4, "invalid checksum") + ErrInvalidClientMessage = errorsmod.Register(ModuleName, 5, "invalid client message") + ErrRetrieveClientID = errorsmod.Register(ModuleName, 6, "failed to retrieve client id") + // Wasm specific + ErrWasmEmptyCode = errorsmod.Register(ModuleName, 7, "empty wasm code") + ErrWasmCodeTooLarge = errorsmod.Register(ModuleName, 8, "wasm code too large") + ErrWasmCodeExists = errorsmod.Register(ModuleName, 9, "wasm code already exists") + ErrWasmChecksumNotFound = errorsmod.Register(ModuleName, 10, "wasm checksum not found") + ErrWasmSubMessagesNotAllowed = errorsmod.Register(ModuleName, 11, "execution of sub messages is not allowed") + ErrWasmEventsNotAllowed = errorsmod.Register(ModuleName, 12, "returning events from a contract is not allowed") + ErrWasmAttributesNotAllowed = errorsmod.Register(ModuleName, 13, "returning attributes from a contract is not allowed") + ErrWasmContractCallFailed = errorsmod.Register(ModuleName, 14, "wasm contract call failed") + ErrWasmInvalidResponseData = errorsmod.Register(ModuleName, 15, "wasm contract returned invalid response data") + ErrWasmInvalidContractModification = errorsmod.Register(ModuleName, 16, "wasm contract made invalid state modifications") +) diff --git a/modules/light-clients/08-wasm/types/events.go b/modules/light-clients/08-wasm/types/events.go new file mode 100644 index 00000000000..c8290fe8579 --- /dev/null +++ b/modules/light-clients/08-wasm/types/events.go @@ -0,0 +1,18 @@ +package types + +// IBC 08-wasm events +const ( + // EventTypeStoreWasmCode defines the event type for bytecode storage + EventTypeStoreWasmCode = "store_wasm_code" + // EventTypeMigrateContract defines the event type for a contract migration + EventTypeMigrateContract = "migrate_contract" + + // AttributeKeyWasmChecksum denotes the checksum of the wasm code that was stored or migrated + AttributeKeyWasmChecksum = "wasm_checksum" + // AttributeKeyClientID denotes the client identifier of the wasm client + AttributeKeyClientID = "client_id" + // AttributeKeyNewChecksum denotes the checksum of the new wasm code. + AttributeKeyNewChecksum = "new_checksum" + + AttributeValueCategory = ModuleName +) diff --git a/modules/light-clients/08-wasm/types/expected_keepers.go b/modules/light-clients/08-wasm/types/expected_keepers.go new file mode 100644 index 00000000000..31df16c3fc5 --- /dev/null +++ b/modules/light-clients/08-wasm/types/expected_keepers.go @@ -0,0 +1,16 @@ +package types + +import ( + storetypes "cosmossdk.io/store/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v8/modules/core/exported" +) + +// ClientKeeper defines the expected client keeper +type ClientKeeper interface { + ClientStore(ctx sdk.Context, clientID string) storetypes.KVStore + GetClientState(ctx sdk.Context, clientID string) (exported.ClientState, bool) + SetClientState(ctx sdk.Context, clientID string, clientState exported.ClientState) +} diff --git a/modules/light-clients/08-wasm/types/export_test.go b/modules/light-clients/08-wasm/types/export_test.go new file mode 100644 index 00000000000..87f50f4c96c --- /dev/null +++ b/modules/light-clients/08-wasm/types/export_test.go @@ -0,0 +1,62 @@ +package types + +import ( + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +/* + This file is to allow for unexported functions and fields to be accessible to the testing package. +*/ + +// MaxWasmSize is the maximum size of a wasm code in bytes. +const MaxWasmSize = maxWasmSize + +var ( + SubjectPrefix = subjectPrefix + SubstitutePrefix = substitutePrefix +) + +// GetClientID is a wrapper around getClientID to allow the function to be directly called in tests. +func GetClientID(clientStore storetypes.KVStore) (string, error) { + return getClientID(clientStore) +} + +// NewMigrateProposalWrappedStore is a wrapper around newMigrateProposalWrappedStore to allow the function to be directly called in tests. +// +//nolint:revive // Returning unexported type for testing purposes. +func NewMigrateProposalWrappedStore(subjectStore, substituteStore storetypes.KVStore) migrateClientWrappedStore { + return newMigrateClientWrappedStore(subjectStore, substituteStore) +} + +// GetStore is a wrapper around getStore to allow the function to be directly called in tests. +func (ws migrateClientWrappedStore) GetStore(key []byte) storetypes.KVStore { + return ws.getStore(key) +} + +// SplitPrefix is a wrapper around splitKey to allow the function to be directly called in tests. +func SplitPrefix(key []byte) ([]byte, []byte) { + return splitPrefix(key) +} + +// WasmQuery wraps wasmQuery and is used solely for testing. +func WasmQuery[T ContractResult](ctx sdk.Context, clientStore storetypes.KVStore, cs *ClientState, payload QueryMsg) (T, error) { + return wasmQuery[T](ctx, clientStore, cs, payload) +} + +// WasmSudo wraps wasmCall and is used solely for testing. +func WasmSudo[T ContractResult](ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *ClientState, payload SudoMsg) (T, error) { + return wasmSudo[T](ctx, cdc, payload, clientStore, cs) +} + +// WasmInstantiate wraps wasmInstantiate and is used solely for testing. +func WasmInstantiate(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *ClientState, payload InstantiateMessage) error { + return wasmInstantiate(ctx, cdc, clientStore, cs, payload) +} + +// WasmMigrate wraps wasmMigrate and is used solely for testing. +func WasmMigrate(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *ClientState, clientID string, payload []byte) error { + return wasmMigrate(ctx, cdc, clientStore, cs, clientID, payload) +} diff --git a/modules/light-clients/08-wasm/types/gas_register.go b/modules/light-clients/08-wasm/types/gas_register.go new file mode 100644 index 00000000000..4ccbcf401af --- /dev/null +++ b/modules/light-clients/08-wasm/types/gas_register.go @@ -0,0 +1,261 @@ +package types + +import ( + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + storetypes "cosmossdk.io/store/types" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +const ( + // DefaultGasMultiplier is how many CosmWasm gas points = 1 Cosmos SDK gas point. + // + // CosmWasm gas strategy is documented in https://github.com/CosmWasm/cosmwasm/blob/v1.0.0-beta/docs/GAS.md. + // Cosmos SDK reference costs can be found here: https://github.com/cosmos/cosmos-sdk/blob/v0.42.10/store/types/gas.go#L198-L209. + // + // The original multiplier of 100 up to CosmWasm 0.16 was based on + // "A write at ~3000 gas and ~200us = 10 gas per us (microsecond) cpu/io + // Rough timing have 88k gas at 90us, which is equal to 1k sdk gas... (one read)" + // as well as manual Wasmer benchmarks from 2019. This was then multiplied by 150_000 + // in the 0.16 -> 1.0 upgrade (https://github.com/CosmWasm/cosmwasm/pull/1120). + // + // The multiplier deserves more reproducible benchmarking and a strategy that allows easy adjustments. + // This is tracked in https://github.com/CosmWasm/wasmd/issues/566 and https://github.com/CosmWasm/wasmd/issues/631. + // Gas adjustments are consensus breaking but may happen in any release marked as consensus breaking. + // Do not make assumptions on how much gas an operation will consume in places that are hard to adjust, + // such as hardcoding them in contracts. + // + // Please note that all gas prices returned to wasmvm should have this multiplied. + // Benchmarks and numbers were discussed in: https://github.com/CosmWasm/wasmd/pull/634#issuecomment-938055852 + DefaultGasMultiplier uint64 = 140_000_000 + // DefaultInstanceCost is how much SDK gas we charge each time we load a WASM instance. + // Creating a new instance is costly, and this helps put a recursion limit to contracts calling contracts. + // Benchmarks and numbers were discussed in: https://github.com/CosmWasm/wasmd/pull/634#issuecomment-938056803 + DefaultInstanceCost uint64 = 60_000 + // DefaultCompileCost is how much SDK gas is charged *per byte* for compiling WASM code. + // Benchmarks and numbers were discussed in: https://github.com/CosmWasm/wasmd/pull/634#issuecomment-938056803 + DefaultCompileCost uint64 = 3 + // DefaultEventAttributeDataCost is how much SDK gas is charged *per byte* for attribute data in events. + // This is used with len(key) + len(value) + DefaultEventAttributeDataCost uint64 = 1 + // DefaultContractMessageDataCost is how much SDK gas is charged *per byte* of the message that goes to the contract + // This is used with len(msg). Note that the message is deserialized in the receiving contract and this is charged + // with wasm gas already. The derserialization of results is also charged in wasmvm. I am unsure if we need to add + // additional costs here. + // Note: also used for error fields on reply, and data on reply. Maybe these should be pulled out to a different (non-zero) field + DefaultContractMessageDataCost uint64 = 0 + // DefaultPerAttributeCost is how much SDK gas we charge per attribute count. + DefaultPerAttributeCost uint64 = 10 + // DefaultPerCustomEventCost is how much SDK gas we charge per event count. + DefaultPerCustomEventCost uint64 = 20 + // DefaultEventAttributeDataFreeTier number of bytes of total attribute data we do not charge. + DefaultEventAttributeDataFreeTier = 100 +) + +// default: 0.15 gas. +// see https://github.com/CosmWasm/wasmd/pull/898#discussion_r937727200 +var defaultPerByteUncompressCost = wasmvmtypes.UFraction{ + Numerator: 15, + Denominator: 100, +} + +// DefaultPerByteUncompressCost is how much SDK gas we charge per source byte to unpack +func DefaultPerByteUncompressCost() wasmvmtypes.UFraction { + return defaultPerByteUncompressCost +} + +// GasRegister abstract source for gas costs +type GasRegister interface { + // NewContractInstanceCosts costs to create a new contract instance from code + NewContractInstanceCosts(pinned bool, msgLen int) storetypes.Gas + // CompileCosts costs to persist and "compile" a new wasm contract + CompileCosts(byteLength int) storetypes.Gas + // UncompressCosts costs to unpack a new wasm contract + UncompressCosts(byteLength int) storetypes.Gas + // InstantiateContractCosts costs when interacting with a wasm contract + InstantiateContractCosts(pinned bool, msgLen int) storetypes.Gas + // ReplyCosts costs to to handle a message reply + ReplyCosts(pinned bool, reply wasmvmtypes.Reply) storetypes.Gas + // EventCosts costs to persist an event + EventCosts(attrs []wasmvmtypes.EventAttribute, events wasmvmtypes.Events) storetypes.Gas + // ToWasmVMGas converts from Cosmos SDK gas units to [CosmWasm gas] (aka. wasmvm gas) + // + // [CosmWasm gas]: https://github.com/CosmWasm/cosmwasm/blob/v1.3.1/docs/GAS.md + ToWasmVMGas(source storetypes.Gas) uint64 + // FromWasmVMGas converts from [CosmWasm gas] (aka. wasmvm gas) to Cosmos SDK gas units + // + // [CosmWasm gas]: https://github.com/CosmWasm/cosmwasm/blob/v1.3.1/docs/GAS.md + FromWasmVMGas(source uint64) storetypes.Gas +} + +// WasmGasRegisterConfig config type +type WasmGasRegisterConfig struct { + // InstanceCost costs when interacting with a wasm contract + InstanceCost storetypes.Gas + // CompileCosts costs to persist and "compile" a new wasm contract + CompileCost storetypes.Gas + // UncompressCost costs per byte to unpack a contract + UncompressCost wasmvmtypes.UFraction + // GasMultiplier is how many cosmwasm gas points = 1 sdk gas point + // SDK reference costs can be found here: https://github.com/cosmos/cosmos-sdk/blob/02c6c9fafd58da88550ab4d7d494724a477c8a68/store/types/gas.go#L153-L164 + GasMultiplier storetypes.Gas + // EventPerAttributeCost is how much SDK gas is charged *per byte* for attribute data in events. + // This is used with len(key) + len(value) + EventPerAttributeCost storetypes.Gas + // EventAttributeDataCost is how much SDK gas is charged *per byte* for attribute data in events. + // This is used with len(key) + len(value) + EventAttributeDataCost storetypes.Gas + // EventAttributeDataFreeTier number of bytes of total attribute data that is free of charge + EventAttributeDataFreeTier uint64 + // ContractMessageDataCost SDK gas charged *per byte* of the message that goes to the contract + // This is used with len(msg) + ContractMessageDataCost storetypes.Gas + // CustomEventCost cost per custom event + CustomEventCost uint64 +} + +// DefaultGasRegisterConfig default values +func DefaultGasRegisterConfig() WasmGasRegisterConfig { + return WasmGasRegisterConfig{ + InstanceCost: DefaultInstanceCost, + CompileCost: DefaultCompileCost, + GasMultiplier: DefaultGasMultiplier, + EventPerAttributeCost: DefaultPerAttributeCost, + CustomEventCost: DefaultPerCustomEventCost, + EventAttributeDataCost: DefaultEventAttributeDataCost, + EventAttributeDataFreeTier: DefaultEventAttributeDataFreeTier, + ContractMessageDataCost: DefaultContractMessageDataCost, + UncompressCost: DefaultPerByteUncompressCost(), + } +} + +// WasmGasRegister implements GasRegister interface +type WasmGasRegister struct { + c WasmGasRegisterConfig +} + +// NewDefaultWasmGasRegister creates instance with default values +func NewDefaultWasmGasRegister() WasmGasRegister { + return NewWasmGasRegister(DefaultGasRegisterConfig()) +} + +// NewWasmGasRegister constructor +func NewWasmGasRegister(c WasmGasRegisterConfig) WasmGasRegister { + if c.GasMultiplier == 0 { + panic(errorsmod.Wrap(sdkerrors.ErrLogic, "GasMultiplier can not be 0")) + } + return WasmGasRegister{ + c: c, + } +} + +// NewContractInstanceCosts costs to create a new contract instance from code +func (g WasmGasRegister) NewContractInstanceCosts(pinned bool, msgLen int) storetypes.Gas { + return g.InstantiateContractCosts(pinned, msgLen) +} + +// CompileCosts costs to persist and "compile" a new wasm contract +func (g WasmGasRegister) CompileCosts(byteLength int) storetypes.Gas { + if byteLength < 0 { + panic(errorsmod.Wrap(ErrInvalid, "negative length")) + } + return g.c.CompileCost * uint64(byteLength) +} + +// UncompressCosts costs to unpack a new wasm contract +func (g WasmGasRegister) UncompressCosts(byteLength int) storetypes.Gas { + if byteLength < 0 { + panic(errorsmod.Wrap(ErrInvalid, "negative length")) + } + return g.c.UncompressCost.Mul(uint64(byteLength)).Floor() +} + +// InstantiateContractCosts costs when interacting with a wasm contract +func (g WasmGasRegister) InstantiateContractCosts(pinned bool, msgLen int) storetypes.Gas { + if msgLen < 0 { + panic(errorsmod.Wrap(ErrInvalid, "negative length")) + } + dataCosts := storetypes.Gas(msgLen) * g.c.ContractMessageDataCost + if pinned { + return dataCosts + } + return g.c.InstanceCost + dataCosts +} + +// ReplyCosts costs to to handle a message reply +func (g WasmGasRegister) ReplyCosts(pinned bool, reply wasmvmtypes.Reply) storetypes.Gas { + var eventGas storetypes.Gas + msgLen := len(reply.Result.Err) + if reply.Result.Ok != nil { + msgLen += len(reply.Result.Ok.Data) + var attrs []wasmvmtypes.EventAttribute + for _, e := range reply.Result.Ok.Events { + eventGas += storetypes.Gas(len(e.Type)) * g.c.EventAttributeDataCost + attrs = append(attrs, e.Attributes...) + } + // apply free tier on the whole set not per event + eventGas += g.EventCosts(attrs, nil) + } + return eventGas + g.InstantiateContractCosts(pinned, msgLen) +} + +// EventCosts costs to persist an event +func (g WasmGasRegister) EventCosts(attrs []wasmvmtypes.EventAttribute, events wasmvmtypes.Events) storetypes.Gas { + gas, remainingFreeTier := g.eventAttributeCosts(attrs, g.c.EventAttributeDataFreeTier) + for _, e := range events { + gas += g.c.CustomEventCost + gas += storetypes.Gas(len(e.Type)) * g.c.EventAttributeDataCost // no free tier with event type + var attrCost storetypes.Gas + attrCost, remainingFreeTier = g.eventAttributeCosts(e.Attributes, remainingFreeTier) + gas += attrCost + } + return gas +} + +func (g WasmGasRegister) eventAttributeCosts(attrs []wasmvmtypes.EventAttribute, freeTier uint64) (storetypes.Gas, uint64) { + if len(attrs) == 0 { + return 0, freeTier + } + var storedBytes uint64 + for _, l := range attrs { + storedBytes += uint64(len(l.Key)) + uint64(len(l.Value)) + } + storedBytes, freeTier = calcWithFreeTier(storedBytes, freeTier) + // total Length * costs + attribute count * costs + r := sdkmath.NewIntFromUint64(g.c.EventAttributeDataCost).Mul(sdkmath.NewIntFromUint64(storedBytes)). + Add(sdkmath.NewIntFromUint64(g.c.EventPerAttributeCost).Mul(sdkmath.NewIntFromUint64(uint64(len(attrs))))) + if !r.IsUint64() { + panic(storetypes.ErrorOutOfGas{Descriptor: "overflow"}) + } + return r.Uint64(), freeTier +} + +// apply free tier +func calcWithFreeTier(storedBytes, freeTier uint64) (uint64, uint64) { + if storedBytes <= freeTier { + return 0, freeTier - storedBytes + } + storedBytes -= freeTier + return storedBytes, 0 +} + +// ToWasmVMGas converts from Cosmos SDK gas units to [CosmWasm gas] (aka. wasmvm gas) +// +// [CosmWasm gas]: https://github.com/CosmWasm/cosmwasm/blob/v1.3.1/docs/GAS.md +func (g WasmGasRegister) ToWasmVMGas(source storetypes.Gas) uint64 { + x := source * g.c.GasMultiplier + if x < source { + panic(storetypes.ErrorOutOfGas{Descriptor: "overflow"}) + } + return x +} + +// FromWasmVMGas converts from [CosmWasm gas] (aka. wasmvm gas) to Cosmos SDK gas units +// +// [CosmWasm gas]: https://github.com/CosmWasm/cosmwasm/blob/v1.3.1/docs/GAS.md +func (g WasmGasRegister) FromWasmVMGas(source uint64) storetypes.Gas { + return source / g.c.GasMultiplier +} diff --git a/modules/light-clients/08-wasm/types/gas_register_custom.go b/modules/light-clients/08-wasm/types/gas_register_custom.go new file mode 100644 index 00000000000..e5a710f9e8e --- /dev/null +++ b/modules/light-clients/08-wasm/types/gas_register_custom.go @@ -0,0 +1,62 @@ +package types + +import ( + "math" + + wasmvm "github.com/CosmWasm/wasmvm" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + + storetypes "cosmossdk.io/store/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// While gas_register.go is a direct copy of https://github.com/CosmWasm/wasmd/blob/main/x/wasm/types/gas_register.go +// This file contains additional constructs that can be maintained separately. +// Most of these functions are slight modifications of keeper function from wasmd, which act on `WasmGasRegister` instead of `Keeper`. +const ( + // DefaultDeserializationCostPerByte The formula should be `len(data) * deserializationCostPerByte` + DefaultDeserializationCostPerByte = 1 +) + +var costJSONDeserialization = wasmvmtypes.UFraction{ + Numerator: DefaultDeserializationCostPerByte * DefaultGasMultiplier, + Denominator: 1, +} + +func (g WasmGasRegister) runtimeGasForContract(ctx sdk.Context) uint64 { + meter := ctx.GasMeter() + if meter.IsOutOfGas() { + return 0 + } + // infinite gas meter with limit=0 or MaxUint64 + if meter.Limit() == 0 || meter.Limit() == math.MaxUint64 { + return math.MaxUint64 + } + return g.ToWasmVMGas(meter.Limit() - meter.GasConsumedToLimit()) +} + +func (g WasmGasRegister) consumeRuntimeGas(ctx sdk.Context, gas uint64) { + consumed := g.FromWasmVMGas(gas) + ctx.GasMeter().ConsumeGas(consumed, "wasm contract") + // throw OutOfGas error if we ran out (got exactly to zero due to better limit enforcing) + if ctx.GasMeter().IsOutOfGas() { + panic(storetypes.ErrorOutOfGas{Descriptor: "Wasmer function execution"}) + } +} + +// MultipliedGasMeter wraps the GasMeter from context and multiplies all reads by out defined multiplier +type MultipliedGasMeter struct { + originalMeter storetypes.GasMeter + GasRegister GasRegister +} + +func NewMultipliedGasMeter(originalMeter storetypes.GasMeter, gr GasRegister) MultipliedGasMeter { + return MultipliedGasMeter{originalMeter: originalMeter, GasRegister: gr} +} + +var _ wasmvm.GasMeter = MultipliedGasMeter{} + +func (m MultipliedGasMeter) GasConsumed() storetypes.Gas { + return m.GasRegister.ToWasmVMGas(m.originalMeter.GasConsumed()) +} diff --git a/modules/light-clients/08-wasm/types/genesis.go b/modules/light-clients/08-wasm/types/genesis.go new file mode 100644 index 00000000000..3cba401a98a --- /dev/null +++ b/modules/light-clients/08-wasm/types/genesis.go @@ -0,0 +1,52 @@ +package types + +import ( + "time" + + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + + "github.com/cosmos/ibc-go/v8/modules/core/exported" +) + +// NewGenesisState creates an 08-wasm GenesisState instance. +func NewGenesisState(contracts []Contract) *GenesisState { + return &GenesisState{Contracts: contracts} +} + +// Validate performs basic genesis state validation returning an error upon any +// failure. +func (gs GenesisState) Validate() error { + for _, contract := range gs.Contracts { + if err := ValidateWasmCode(contract.CodeBytes); err != nil { + return errorsmod.Wrap(err, "wasm bytecode validation failed") + } + } + + return nil +} + +// 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 storetypes.KVStore) []exported.GenesisMetadata { + payload := QueryMsg{ + ExportMetadata: &ExportMetadataMsg{}, + } + + ctx := sdk.NewContext(nil, tmproto.Header{Height: 1, Time: time.Now()}, true, nil) // context with infinite gas meter + result, err := wasmQuery[ExportMetadataResult](ctx, store, &cs, payload) + if err != nil { + panic(err) + } + + genesisMetadata := make([]exported.GenesisMetadata, len(result.GenesisMetadata)) + for i, metadata := range result.GenesisMetadata { + genesisMetadata[i] = metadata + } + + return genesisMetadata +} diff --git a/modules/light-clients/08-wasm/types/genesis.pb.go b/modules/light-clients/08-wasm/types/genesis.pb.go new file mode 100644 index 00000000000..d142dfd1ddb --- /dev/null +++ b/modules/light-clients/08-wasm/types/genesis.pb.go @@ -0,0 +1,504 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/lightclients/wasm/v1/genesis.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// GenesisState defines 08-wasm's keeper genesis state +type GenesisState struct { + // uploaded light client wasm contracts + Contracts []Contract `protobuf:"bytes,1,rep,name=contracts,proto3" json:"contracts"` +} + +func (m *GenesisState) Reset() { *m = GenesisState{} } +func (m *GenesisState) String() string { return proto.CompactTextString(m) } +func (*GenesisState) ProtoMessage() {} +func (*GenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_05e250654f164e20, []int{0} +} +func (m *GenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisState.Merge(m, src) +} +func (m *GenesisState) XXX_Size() int { + return m.Size() +} +func (m *GenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisState proto.InternalMessageInfo + +func (m *GenesisState) GetContracts() []Contract { + if m != nil { + return m.Contracts + } + return nil +} + +// Contract stores contract code +type Contract struct { + // contract byte code + CodeBytes []byte `protobuf:"bytes,1,opt,name=code_bytes,json=codeBytes,proto3" json:"code_bytes,omitempty"` +} + +func (m *Contract) Reset() { *m = Contract{} } +func (m *Contract) String() string { return proto.CompactTextString(m) } +func (*Contract) ProtoMessage() {} +func (*Contract) Descriptor() ([]byte, []int) { + return fileDescriptor_05e250654f164e20, []int{1} +} +func (m *Contract) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Contract) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Contract.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Contract) XXX_Merge(src proto.Message) { + xxx_messageInfo_Contract.Merge(m, src) +} +func (m *Contract) XXX_Size() int { + return m.Size() +} +func (m *Contract) XXX_DiscardUnknown() { + xxx_messageInfo_Contract.DiscardUnknown(m) +} + +var xxx_messageInfo_Contract proto.InternalMessageInfo + +func init() { + proto.RegisterType((*GenesisState)(nil), "ibc.lightclients.wasm.v1.GenesisState") + proto.RegisterType((*Contract)(nil), "ibc.lightclients.wasm.v1.Contract") +} + +func init() { + proto.RegisterFile("ibc/lightclients/wasm/v1/genesis.proto", fileDescriptor_05e250654f164e20) +} + +var fileDescriptor_05e250654f164e20 = []byte{ + // 261 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0xcb, 0x4c, 0x4a, 0xd6, + 0xcf, 0xc9, 0x4c, 0xcf, 0x28, 0x49, 0xce, 0xc9, 0x4c, 0xcd, 0x2b, 0x29, 0xd6, 0x2f, 0x4f, 0x2c, + 0xce, 0xd5, 0x2f, 0x33, 0xd4, 0x4f, 0x4f, 0xcd, 0x4b, 0x2d, 0xce, 0x2c, 0xd6, 0x2b, 0x28, 0xca, + 0x2f, 0xc9, 0x17, 0x92, 0xc8, 0x4c, 0x4a, 0xd6, 0x43, 0x56, 0xa7, 0x07, 0x52, 0xa7, 0x57, 0x66, + 0x28, 0x25, 0x92, 0x9e, 0x9f, 0x9e, 0x0f, 0x56, 0xa4, 0x0f, 0x62, 0x41, 0xd4, 0x2b, 0x85, 0x71, + 0xf1, 0xb8, 0x43, 0x0c, 0x08, 0x2e, 0x49, 0x2c, 0x49, 0x15, 0x72, 0xe3, 0xe2, 0x4c, 0xce, 0xcf, + 0x2b, 0x29, 0x4a, 0x4c, 0x2e, 0x29, 0x96, 0x60, 0x54, 0x60, 0xd6, 0xe0, 0x36, 0x52, 0xd2, 0xc3, + 0x65, 0xa6, 0x9e, 0x33, 0x54, 0xa9, 0x13, 0xcb, 0x89, 0x7b, 0xf2, 0x0c, 0x41, 0x08, 0xad, 0x4a, + 0xfa, 0x5c, 0x1c, 0x30, 0x49, 0x21, 0x59, 0x2e, 0xae, 0xe4, 0xfc, 0x94, 0xd4, 0xf8, 0xa4, 0xca, + 0x92, 0x54, 0x90, 0xa1, 0x8c, 0x1a, 0x3c, 0x20, 0xa5, 0x29, 0xa9, 0x4e, 0x20, 0x01, 0x2b, 0x96, + 0x8e, 0x05, 0xf2, 0x0c, 0x4e, 0x61, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, + 0x91, 0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, 0xcb, 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, + 0x65, 0x93, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0xab, 0x9f, 0x9c, 0x5f, 0x9c, + 0x9b, 0x5f, 0xac, 0x9f, 0x99, 0x94, 0xac, 0x9b, 0x9e, 0xaf, 0x9f, 0x9b, 0x9f, 0x52, 0x9a, 0x93, + 0x5a, 0x0c, 0x09, 0x17, 0x5d, 0x58, 0xc0, 0x18, 0x58, 0xe8, 0x82, 0xc3, 0xa6, 0xa4, 0xb2, 0x20, + 0xb5, 0x38, 0x89, 0x0d, 0xec, 0x4f, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x06, 0xb3, 0x17, + 0x15, 0x41, 0x01, 0x00, 0x00, +} + +func (m *GenesisState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Contracts) > 0 { + for iNdEx := len(m.Contracts) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Contracts[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *Contract) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Contract) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Contract) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.CodeBytes) > 0 { + i -= len(m.CodeBytes) + copy(dAtA[i:], m.CodeBytes) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.CodeBytes))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { + offset -= sovGenesis(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *GenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Contracts) > 0 { + for _, e := range m.Contracts { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + return n +} + +func (m *Contract) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.CodeBytes) + if l > 0 { + n += 1 + l + sovGenesis(uint64(l)) + } + return n +} + +func sovGenesis(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenesis(x uint64) (n int) { + return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GenesisState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Contracts", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Contracts = append(m.Contracts, Contract{}) + if err := m.Contracts[len(m.Contracts)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Contract) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Contract: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Contract: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CodeBytes", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CodeBytes = append(m.CodeBytes[:0], dAtA[iNdEx:postIndex]...) + if m.CodeBytes == nil { + m.CodeBytes = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenesis(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenesis + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenesis + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenesis + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/light-clients/08-wasm/types/genesis_test.go b/modules/light-clients/08-wasm/types/genesis_test.go new file mode 100644 index 00000000000..026b6a901c8 --- /dev/null +++ b/modules/light-clients/08-wasm/types/genesis_test.go @@ -0,0 +1,126 @@ +package types_test + +import ( + "encoding/json" + + wasmvm "github.com/CosmWasm/wasmvm" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + + errorsmod "cosmossdk.io/errors" + + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v8/modules/core/exported" +) + +func (suite *TypesTestSuite) TestValidateGenesis() { + testCases := []struct { + name string + genState *types.GenesisState + expPass bool + }{ + { + "valid genesis", + &types.GenesisState{ + Contracts: []types.Contract{{CodeBytes: []byte{1}}}, + }, + true, + }, + { + "invalid genesis", + &types.GenesisState{ + Contracts: []types.Contract{{CodeBytes: []byte{}}}, + }, + false, + }, + } + + for _, tc := range testCases { + tc := tc + err := tc.genState.Validate() + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + } +} + +func (suite *TypesTestSuite) TestExportMetatada() { + mockMetadata := clienttypes.NewGenesisMetadata([]byte("key"), []byte("value")) + + testCases := []struct { + name string + malleate func() + expPanic error + expMetadata []exported.GenesisMetadata + }{ + { + "success", + func() { + suite.mockVM.RegisterQueryCallback(types.ExportMetadataMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, queryMsg []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) ([]byte, uint64, error) { + var msg types.QueryMsg + + err := json.Unmarshal(queryMsg, &msg) + suite.Require().NoError(err) + + suite.Require().NotNil(msg.ExportMetadata) + suite.Require().Nil(msg.VerifyClientMessage) + suite.Require().Nil(msg.Status) + suite.Require().Nil(msg.CheckForMisbehaviour) + suite.Require().Nil(msg.TimestampAtHeight) + + resp, err := json.Marshal(types.ExportMetadataResult{ + GenesisMetadata: []clienttypes.GenesisMetadata{mockMetadata}, + }) + suite.Require().NoError(err) + + return resp, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + []exported.GenesisMetadata{mockMetadata}, + }, + { + "failure: contract returns an error", + func() { + suite.mockVM.RegisterQueryCallback(types.ExportMetadataMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, queryMsg []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) ([]byte, uint64, error) { + return nil, 0, wasmtesting.ErrMockContract + }) + }, + errorsmod.Wrapf(types.ErrWasmContractCallFailed, wasmtesting.ErrMockContract.Error()), + nil, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + + clientState := endpoint.GetClientState() + + tc.malleate() + + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) + + var metadata []exported.GenesisMetadata + exportMetadata := func() { + metadata = clientState.ExportMetadata(store) + } + + if tc.expPanic == nil { + exportMetadata() + + suite.Require().Equal(tc.expMetadata, metadata) + } else { + suite.Require().PanicsWithError(tc.expPanic.Error(), exportMetadata) + } + }) + } +} diff --git a/modules/light-clients/08-wasm/types/keys.go b/modules/light-clients/08-wasm/types/keys.go new file mode 100644 index 00000000000..a02f69974b8 --- /dev/null +++ b/modules/light-clients/08-wasm/types/keys.go @@ -0,0 +1,9 @@ +package types + +const ( + // ModuleName for the wasm client + ModuleName = "08-wasm" + + // StoreKey is the store key string for 08-wasm + StoreKey = ModuleName +) diff --git a/modules/light-clients/08-wasm/types/migrate_contract.go b/modules/light-clients/08-wasm/types/migrate_contract.go new file mode 100644 index 00000000000..f028c2c02fa --- /dev/null +++ b/modules/light-clients/08-wasm/types/migrate_contract.go @@ -0,0 +1,41 @@ +package types + +import ( + "bytes" + "encoding/hex" + + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// MigrateContract calls the migrate entry point on the contract with the given +// migrateMsg. The contract must exist and the checksum must be found in the +// store. If the checksum is the same as the current checksum, an error is returned. +// This does not update the checksum in the client state. +func (cs ClientState) MigrateContract( + ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, + clientID string, newChecksum, migrateMsg []byte, +) error { + if !HasChecksum(ctx, newChecksum) { + return ErrWasmChecksumNotFound + } + + if bytes.Equal(cs.Checksum, newChecksum) { + return errorsmod.Wrapf(ErrWasmCodeExists, "new checksum (%s) is the same as current checksum (%s)", hex.EncodeToString(newChecksum), hex.EncodeToString(cs.Checksum)) + } + + // update the checksum, this needs to be done before the contract migration + // so that wasmMigrate can call the right code. Note that this is not + // persisted to the client store. + cs.Checksum = newChecksum + + err := wasmMigrate(ctx, cdc, clientStore, &cs, clientID, migrateMsg) + if err != nil { + return err + } + + return nil +} diff --git a/modules/light-clients/08-wasm/types/migrate_contract_test.go b/modules/light-clients/08-wasm/types/migrate_contract_test.go new file mode 100644 index 00000000000..cf96c923c56 --- /dev/null +++ b/modules/light-clients/08-wasm/types/migrate_contract_test.go @@ -0,0 +1,139 @@ +package types_test + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + + wasmvm "github.com/CosmWasm/wasmvm" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" +) + +func (suite *TypesTestSuite) TestMigrateContract() { + var ( + oldHash [32]byte + newHash [32]byte + payload []byte + expClientState *types.ClientState + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success: no update to client state", + func() { + err := ibcwasm.Checksums.Set(suite.chainA.GetContext(), newHash[:]) + suite.Require().NoError(err) + + payload = []byte{1} + expChecksum := wasmvmtypes.ForceNewChecksum(hex.EncodeToString(newHash[:])) + + suite.mockVM.MigrateFn = func(checksum wasmvm.Checksum, env wasmvmtypes.Env, msg []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + suite.Require().Equal(expChecksum, checksum) + suite.Require().Equal(defaultWasmClientID, env.Contract.Address) + suite.Require().Equal(payload, msg) + + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.Response{Data: data}, wasmtesting.DefaultGasUsed, nil + } + }, + nil, + }, + { + "success: update client state", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + expClientState = types.NewClientState([]byte{1}, newHash[:], clienttypes.NewHeight(2000, 2)) + store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), expClientState)) + + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.Response{Data: data}, wasmtesting.DefaultGasUsed, nil + } + }, + nil, + }, + { + "failure: new and old checksum are the same", + func() { + newHash = oldHash + // this should not be called + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + panic("unreachable") + } + }, + types.ErrWasmCodeExists, + }, + { + "failure: checksum not found", + func() { + err := ibcwasm.Checksums.Remove(suite.chainA.GetContext(), newHash[:]) + suite.Require().NoError(err) + }, + types.ErrWasmChecksumNotFound, + }, + { + "failure: contract returns error", + func() { + err := ibcwasm.Checksums.Set(suite.chainA.GetContext(), newHash[:]) + suite.Require().NoError(err) + + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + return nil, wasmtesting.DefaultGasUsed, wasmtesting.ErrMockContract + } + }, + types.ErrWasmContractCallFailed, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + oldHash = sha256.Sum256(wasmtesting.Code) + newHash = sha256.Sum256([]byte{1, 2, 3}) + + err := ibcwasm.Checksums.Set(suite.chainA.GetContext(), newHash[:]) + suite.Require().NoError(err) + + endpointA := wasmtesting.NewWasmEndpoint(suite.chainA) + err = endpointA.CreateClient() + suite.Require().NoError(err) + + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpointA.ClientID) + clientState, ok := endpointA.GetClientState().(*types.ClientState) + suite.Require().True(ok) + + expClientState = clientState + + tc.malleate() + + err = clientState.MigrateContract(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), clientStore, endpointA.ClientID, newHash[:], payload) + + // updated client state + clientState, ok = endpointA.GetClientState().(*types.ClientState) + suite.Require().True(ok) + + expPass := tc.expErr == nil + if expPass { + suite.Require().NoError(err) + suite.Require().Equal(expClientState, clientState) + } else { + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} diff --git a/modules/light-clients/08-wasm/types/misbehaviour_handle.go b/modules/light-clients/08-wasm/types/misbehaviour_handle.go new file mode 100644 index 00000000000..16a234b44b8 --- /dev/null +++ b/modules/light-clients/08-wasm/types/misbehaviour_handle.go @@ -0,0 +1,30 @@ +package types + +import ( + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v8/modules/core/exported" +) + +// CheckForMisbehaviour detects misbehaviour in a submitted Header message and verifies +// the correctness of a submitted Misbehaviour ClientMessage +func (cs ClientState) CheckForMisbehaviour(ctx sdk.Context, _ codec.BinaryCodec, clientStore storetypes.KVStore, clientMsg exported.ClientMessage) bool { + clientMessage, ok := clientMsg.(*ClientMessage) + if !ok { + return false + } + + payload := QueryMsg{ + CheckForMisbehaviour: &CheckForMisbehaviourMsg{ClientMessage: clientMessage}, + } + + result, err := wasmQuery[CheckForMisbehaviourResult](ctx, clientStore, &cs, payload) + if err != nil { + return false + } + + return result.FoundMisbehaviour +} diff --git a/modules/light-clients/08-wasm/types/misbehaviour_handle_test.go b/modules/light-clients/08-wasm/types/misbehaviour_handle_test.go new file mode 100644 index 00000000000..13f4489c742 --- /dev/null +++ b/modules/light-clients/08-wasm/types/misbehaviour_handle_test.go @@ -0,0 +1,92 @@ +package types_test + +import ( + "encoding/json" + "errors" + + wasmvm "github.com/CosmWasm/wasmvm" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + "github.com/cosmos/ibc-go/v8/modules/core/exported" + ibctmtypes "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" +) + +func (suite *TypesTestSuite) TestCheckForMisbehaviour() { + var clientMessage exported.ClientMessage + + testCases := []struct { + name string + malleate func() + expFoundMisbehaviour bool + }{ + { + "success: no misbehaviour", + func() { + suite.mockVM.RegisterQueryCallback(types.CheckForMisbehaviourMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) ([]byte, uint64, error) { + resp, err := json.Marshal(types.CheckForMisbehaviourResult{FoundMisbehaviour: false}) + suite.Require().NoError(err) + return resp, wasmtesting.DefaultGasUsed, nil + }) + }, + false, + }, + { + "success: misbehaviour found", func() { + suite.mockVM.RegisterQueryCallback(types.CheckForMisbehaviourMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) ([]byte, uint64, error) { + resp, err := json.Marshal(types.CheckForMisbehaviourResult{FoundMisbehaviour: true}) + suite.Require().NoError(err) + return resp, wasmtesting.DefaultGasUsed, nil + }) + }, + true, + }, + { + "success: contract error, resp cannot be marshalled", func() { + suite.mockVM.RegisterQueryCallback(types.CheckForMisbehaviourMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) ([]byte, uint64, error) { + resp := "cannot be unmarshalled" + return []byte(resp), wasmtesting.DefaultGasUsed, nil + }) + }, + false, + }, + { + "success: vm returns error, ", func() { + suite.mockVM.RegisterQueryCallback(types.CheckForMisbehaviourMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) ([]byte, uint64, error) { + return nil, 0, errors.New("invalid block ID") + }) + }, + false, + }, + { + "success: invalid client message", func() { + clientMessage = &ibctmtypes.Header{} + // we will not register the callback here because this test case does not reach the VM + }, + false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + // reset suite to create fresh application state + suite.SetupWasmWithMockVM() + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + + clientState := endpoint.GetClientState() + clientMessage = &types.ClientMessage{ + Data: []byte{1}, + } + + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) + + tc.malleate() + + foundMisbehaviour := clientState.CheckForMisbehaviour(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), clientStore, clientMessage) + suite.Require().Equal(tc.expFoundMisbehaviour, foundMisbehaviour) + }) + } +} diff --git a/modules/light-clients/08-wasm/types/msgs.go b/modules/light-clients/08-wasm/types/msgs.go new file mode 100644 index 00000000000..000716469e3 --- /dev/null +++ b/modules/light-clients/08-wasm/types/msgs.go @@ -0,0 +1,94 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + ibcerrors "github.com/cosmos/ibc-go/v8/modules/core/errors" +) + +var ( + _ sdk.Msg = (*MsgStoreCode)(nil) + _ sdk.Msg = (*MsgMigrateContract)(nil) + _ sdk.Msg = (*MsgRemoveChecksum)(nil) + _ sdk.HasValidateBasic = (*MsgStoreCode)(nil) + _ sdk.HasValidateBasic = (*MsgMigrateContract)(nil) + _ sdk.HasValidateBasic = (*MsgRemoveChecksum)(nil) +) + +// MsgStoreCode creates a new MsgStoreCode instance +func NewMsgStoreCode(signer string, code []byte) *MsgStoreCode { + return &MsgStoreCode{ + Signer: signer, + WasmByteCode: code, + } +} + +// ValidateBasic implements sdk.HasValidateBasic +func (m MsgStoreCode) ValidateBasic() error { + if err := ValidateWasmCode(m.WasmByteCode); err != nil { + return err + } + + _, err := sdk.AccAddressFromBech32(m.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + + return nil +} + +// NewMsgRemoveChecksum creates a new MsgRemoveChecksum instance +func NewMsgRemoveChecksum(signer string, checksum []byte) *MsgRemoveChecksum { + return &MsgRemoveChecksum{ + Signer: signer, + Checksum: checksum, + } +} + +// ValidateBasic implements sdk.HasValidateBasic +func (m MsgRemoveChecksum) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(m.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + + if err := ValidateWasmChecksum(m.Checksum); err != nil { + return err + } + + return nil +} + +// MsgMigrateContract creates a new MsgMigrateContract instance +func NewMsgMigrateContract(signer, clientID string, checksum, migrateMsg []byte) *MsgMigrateContract { + return &MsgMigrateContract{ + Signer: signer, + ClientId: clientID, + Checksum: checksum, + Msg: migrateMsg, + } +} + +// ValidateBasic implements sdk.HasValidateBasic +func (m MsgMigrateContract) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(m.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + + if err := ValidateWasmChecksum(m.Checksum); err != nil { + return err + } + + if err := ValidateClientID(m.ClientId); err != nil { + return err + } + + if len(m.Msg) == 0 { + return errorsmod.Wrap(ibcerrors.ErrInvalidRequest, "migrate message cannot be empty") + } + + return nil +} diff --git a/modules/light-clients/08-wasm/types/msgs_test.go b/modules/light-clients/08-wasm/types/msgs_test.go new file mode 100644 index 00000000000..f3647cb2fe6 --- /dev/null +++ b/modules/light-clients/08-wasm/types/msgs_test.go @@ -0,0 +1,265 @@ +package types_test + +import ( + "crypto/sha256" + "testing" + + "github.com/stretchr/testify/require" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v8/modules/core/errors" + ibctesting "github.com/cosmos/ibc-go/v8/testing" +) + +func TestMsgStoreCodeValidateBasic(t *testing.T) { + signer := sdk.AccAddress(ibctesting.TestAccAddress).String() + testCases := []struct { + name string + msg *types.MsgStoreCode + expErr error + }{ + { + "success: valid signer address, valid length code", + types.NewMsgStoreCode(signer, wasmtesting.Code), + nil, + }, + { + "failure: code is empty", + types.NewMsgStoreCode(signer, []byte("")), + types.ErrWasmEmptyCode, + }, + { + "failure: code is too large", + types.NewMsgStoreCode(signer, make([]byte, types.MaxWasmSize+1)), + types.ErrWasmCodeTooLarge, + }, + { + "failure: signer is invalid", + types.NewMsgStoreCode("invalid", wasmtesting.Code), + ibcerrors.ErrInvalidAddress, + }, + } + + for _, tc := range testCases { + tc := tc + + err := tc.msg.ValidateBasic() + expPass := tc.expErr == nil + if expPass { + require.NoError(t, err) + } else { + require.ErrorIs(t, err, tc.expErr) + } + } +} + +func (suite *TypesTestSuite) TestMsgStoreCodeGetSigners() { + testCases := []struct { + name string + address sdk.AccAddress + expPass bool + }{ + {"success: valid address", sdk.AccAddress(ibctesting.TestAccAddress), true}, + {"failure: nil address", nil, false}, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + address := tc.address + msg := types.NewMsgStoreCode(address.String(), wasmtesting.Code) + + signers, _, err := GetSimApp(suite.chainA).AppCodec().GetMsgV1Signers(msg) + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(address.Bytes(), signers[0]) + } else { + suite.Require().Error(err) + } + }) + } +} + +func TestMsgMigrateContractValidateBasic(t *testing.T) { + signer := sdk.AccAddress(ibctesting.TestAccAddress).String() + validChecksum := sha256.Sum256(wasmtesting.Code) + validMigrateMsg := []byte("{}") + + testCases := []struct { + name string + msg *types.MsgMigrateContract + expErr error + }{ + { + "success: valid signer address, valid checksum, valid migrate msg", + types.NewMsgMigrateContract(signer, defaultWasmClientID, validChecksum[:], validMigrateMsg), + nil, + }, + { + "failure: invalid signer address", + types.NewMsgMigrateContract(ibctesting.InvalidID, defaultWasmClientID, validChecksum[:], validMigrateMsg), + ibcerrors.ErrInvalidAddress, + }, + { + "failure: clientID is not a valid client identifier", + types.NewMsgMigrateContract(signer, ibctesting.InvalidID, validChecksum[:], validMigrateMsg), + host.ErrInvalidID, + }, + { + "failure: clientID is not a wasm client identifier", + types.NewMsgMigrateContract(signer, ibctesting.FirstClientID, validChecksum[:], validMigrateMsg), + host.ErrInvalidID, + }, + { + "failure: checksum is nil", + types.NewMsgMigrateContract(signer, defaultWasmClientID, nil, validMigrateMsg), + errorsmod.Wrap(types.ErrInvalidChecksum, "checksum cannot be empty"), + }, + { + "failure: checksum is empty", + types.NewMsgMigrateContract(signer, defaultWasmClientID, []byte{}, validMigrateMsg), + errorsmod.Wrap(types.ErrInvalidChecksum, "checksum cannot be empty"), + }, + { + "failure: checksum is not 32 bytes", + types.NewMsgMigrateContract(signer, defaultWasmClientID, []byte{1}, validMigrateMsg), + errorsmod.Wrapf(types.ErrInvalidChecksum, "expected length of 32 bytes, got %d", 1), + }, + { + "failure: migrateMsg is nil", + types.NewMsgMigrateContract(signer, defaultWasmClientID, validChecksum[:], nil), + errorsmod.Wrap(ibcerrors.ErrInvalidRequest, "migrate message cannot be empty"), + }, + { + "failure: migrateMsg is empty", + types.NewMsgMigrateContract(signer, defaultWasmClientID, validChecksum[:], []byte("")), + errorsmod.Wrap(ibcerrors.ErrInvalidRequest, "migrate message cannot be empty"), + }, + } + + for _, tc := range testCases { + tc := tc + + err := tc.msg.ValidateBasic() + expPass := tc.expErr == nil + if expPass { + require.NoError(t, err) + } else { + require.ErrorIs(t, err, tc.expErr, tc.name) + } + } +} + +func (suite *TypesTestSuite) TestMsgMigrateContractGetSigners() { + checksum := sha256.Sum256(wasmtesting.Code) + + testCases := []struct { + name string + address sdk.AccAddress + expPass bool + }{ + {"success: valid address", sdk.AccAddress(ibctesting.TestAccAddress), true}, + {"failure: nil address", nil, false}, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + address := tc.address + msg := types.NewMsgMigrateContract(address.String(), defaultWasmClientID, checksum[:], []byte("{}")) + + signers, _, err := GetSimApp(suite.chainA).AppCodec().GetMsgV1Signers(msg) + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(address.Bytes(), signers[0]) + } else { + suite.Require().Error(err) + } + }) + } +} + +func TestMsgRemoveChecksumValidateBasic(t *testing.T) { + signer := sdk.AccAddress(ibctesting.TestAccAddress).String() + checksum := sha256.Sum256(wasmtesting.Code) + + testCases := []struct { + name string + msg *types.MsgRemoveChecksum + expErr error + }{ + { + "success: valid signer address, valid length checksum", + types.NewMsgRemoveChecksum(signer, checksum[:]), + nil, + }, + { + "failure: checksum is empty", + types.NewMsgRemoveChecksum(signer, []byte("")), + types.ErrInvalidChecksum, + }, + { + "failure: checksum is nil", + types.NewMsgRemoveChecksum(signer, nil), + types.ErrInvalidChecksum, + }, + { + "failure: signer is invalid", + types.NewMsgRemoveChecksum(ibctesting.InvalidID, checksum[:]), + ibcerrors.ErrInvalidAddress, + }, + } + + for _, tc := range testCases { + tc := tc + + err := tc.msg.ValidateBasic() + + if tc.expErr == nil { + require.NoError(t, err, tc.name) + } else { + require.ErrorIs(t, err, tc.expErr, tc.name) + } + } +} + +func (suite *TypesTestSuite) TestMsgRemoveChecksumGetSigners() { + checksum := sha256.Sum256(wasmtesting.Code) + + testCases := []struct { + name string + address sdk.AccAddress + expPass bool + }{ + {"success: valid address", sdk.AccAddress(ibctesting.TestAccAddress), true}, + {"failure: nil address", nil, false}, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + address := tc.address + msg := types.NewMsgRemoveChecksum(address.String(), checksum[:]) + + signers, _, err := GetSimApp(suite.chainA).AppCodec().GetMsgV1Signers(msg) + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(address.Bytes(), signers[0]) + } else { + suite.Require().Error(err) + } + }) + } +} diff --git a/modules/light-clients/08-wasm/types/proposal_handle.go b/modules/light-clients/08-wasm/types/proposal_handle.go new file mode 100644 index 00000000000..fc62785a5a5 --- /dev/null +++ b/modules/light-clients/08-wasm/types/proposal_handle.go @@ -0,0 +1,42 @@ +package types + +import ( + "bytes" + "encoding/hex" + + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v8/modules/core/exported" +) + +// CheckSubstituteAndUpdateState will verify that a substitute client state is valid and update the subject client state. +// Note that this method is used only for recovery and will not allow changes to the checksum. +func (cs ClientState) CheckSubstituteAndUpdateState(ctx sdk.Context, cdc codec.BinaryCodec, subjectClientStore, substituteClientStore storetypes.KVStore, substituteClient exported.ClientState) error { + substituteClientState, ok := substituteClient.(*ClientState) + if !ok { + return errorsmod.Wrapf( + clienttypes.ErrInvalidClient, + "invalid substitute client state: expected type %T, got %T", &ClientState{}, substituteClient, + ) + } + + // check that checksums of subject client state and substitute client state match + // changing the checksum is only allowed through the migrate contract RPC endpoint + if !bytes.Equal(cs.Checksum, substituteClientState.Checksum) { + return errorsmod.Wrapf(clienttypes.ErrInvalidClient, "expected checksums to be equal: expected %s, got %s", hex.EncodeToString(cs.Checksum), hex.EncodeToString(substituteClientState.Checksum)) + } + + store := newMigrateClientWrappedStore(subjectClientStore, substituteClientStore) + + payload := SudoMsg{ + MigrateClientStore: &MigrateClientStoreMsg{}, + } + + _, err := wasmSudo[EmptyResult](ctx, cdc, payload, store, &cs) + return err +} diff --git a/modules/light-clients/08-wasm/types/proposal_handle_test.go b/modules/light-clients/08-wasm/types/proposal_handle_test.go new file mode 100644 index 00000000000..812dd501f1b --- /dev/null +++ b/modules/light-clients/08-wasm/types/proposal_handle_test.go @@ -0,0 +1,142 @@ +package types_test + +import ( + "encoding/json" + + cosmwasm "github.com/CosmWasm/wasmvm" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + + storetypes "cosmossdk.io/store/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + "github.com/cosmos/ibc-go/v8/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" +) + +func (suite *TypesTestSuite) TestCheckSubstituteAndUpdateState() { + var substituteClientState exported.ClientState + var expectedClientStateBz []byte + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", + func() { + suite.mockVM.RegisterSudoCallback( + types.MigrateClientStoreMsg{}, + func(_ cosmwasm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, store cosmwasm.KVStore, _ cosmwasm.GoAPI, _ cosmwasm.Querier, _ cosmwasm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + var payload types.SudoMsg + err := json.Unmarshal(sudoMsg, &payload) + suite.Require().NoError(err) + + suite.Require().NotNil(payload.MigrateClientStore) + suite.Require().Nil(payload.UpdateState) + suite.Require().Nil(payload.UpdateStateOnMisbehaviour) + suite.Require().Nil(payload.VerifyMembership) + suite.Require().Nil(payload.VerifyNonMembership) + suite.Require().Nil(payload.VerifyUpgradeAndUpdateState) + + bz, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + prefixedKey := types.SubjectPrefix + prefixedKey = append(prefixedKey, host.ClientStateKey()...) + expectedClientStateBz = wasmtesting.CreateMockClientStateBz(suite.chainA.Codec, suite.checksum) + store.Set(prefixedKey, expectedClientStateBz) + + return &wasmvmtypes.Response{Data: bz}, wasmtesting.DefaultGasUsed, nil + }, + ) + }, + nil, + }, + { + "failure: invalid substitute client state", + func() { + substituteClientState = &ibctm.ClientState{} + }, + clienttypes.ErrInvalidClient, + }, + { + "failure: checksums do not match", + func() { + substituteClientState = &types.ClientState{ + Checksum: []byte("invalid"), + } + }, + clienttypes.ErrInvalidClient, + }, + { + "failure: contract returns error", + func() { + suite.mockVM.RegisterSudoCallback( + types.MigrateClientStoreMsg{}, + func(_ cosmwasm.Checksum, _ wasmvmtypes.Env, _ []byte, _ cosmwasm.KVStore, _ cosmwasm.GoAPI, _ cosmwasm.Querier, _ cosmwasm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + return nil, wasmtesting.DefaultGasUsed, wasmtesting.ErrMockContract + }, + ) + }, + types.ErrWasmContractCallFailed, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + expectedClientStateBz = nil + + endpointA := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpointA.CreateClient() + suite.Require().NoError(err) + + subjectClientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpointA.ClientID) + subjectClientState := endpointA.GetClientState() + + substituteEndpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err = substituteEndpoint.CreateClient() + suite.Require().NoError(err) + + substituteClientState = substituteEndpoint.GetClientState() + substituteClientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), substituteEndpoint.ClientID) + + tc.malleate() + + err = subjectClientState.CheckSubstituteAndUpdateState( + suite.chainA.GetContext(), + suite.chainA.Codec, + subjectClientStore, + substituteClientStore, + substituteClientState, + ) + + expPass := tc.expErr == nil + if expPass { + suite.Require().NoError(err) + + clientStateBz := subjectClientStore.Get(host.ClientStateKey()) + suite.Require().Equal(expectedClientStateBz, clientStateBz) + } else { + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func GetProcessedHeight(clientStore storetypes.KVStore, height exported.Height) (uint64, bool) { + key := ibctm.ProcessedHeightKey(height) + bz := clientStore.Get(key) + if len(bz) == 0 { + return 0, false + } + + return sdk.BigEndianToUint64(bz), true +} diff --git a/modules/light-clients/08-wasm/types/query.pb.go b/modules/light-clients/08-wasm/types/query.pb.go new file mode 100644 index 00000000000..325d5e0dccd --- /dev/null +++ b/modules/light-clients/08-wasm/types/query.pb.go @@ -0,0 +1,1053 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/lightclients/wasm/v1/query.proto + +package types + +import ( + context "context" + fmt "fmt" + query "github.com/cosmos/cosmos-sdk/types/query" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// QueryChecksumsRequest is the request type for the Query/Checksums RPC method. +type QueryChecksumsRequest struct { + // pagination defines an optional pagination for the request. + Pagination *query.PageRequest `protobuf:"bytes,1,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryChecksumsRequest) Reset() { *m = QueryChecksumsRequest{} } +func (m *QueryChecksumsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryChecksumsRequest) ProtoMessage() {} +func (*QueryChecksumsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_9e3718a8cb915777, []int{0} +} +func (m *QueryChecksumsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryChecksumsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryChecksumsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryChecksumsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryChecksumsRequest.Merge(m, src) +} +func (m *QueryChecksumsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryChecksumsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryChecksumsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryChecksumsRequest proto.InternalMessageInfo + +func (m *QueryChecksumsRequest) GetPagination() *query.PageRequest { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryChecksumsResponse is the response type for the Query/Checksums RPC method. +type QueryChecksumsResponse struct { + // checksums is a list of the hex encoded checksums of all wasm codes stored. + Checksums []string `protobuf:"bytes,1,rep,name=checksums,proto3" json:"checksums,omitempty"` + // pagination defines the pagination in the response. + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryChecksumsResponse) Reset() { *m = QueryChecksumsResponse{} } +func (m *QueryChecksumsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryChecksumsResponse) ProtoMessage() {} +func (*QueryChecksumsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_9e3718a8cb915777, []int{1} +} +func (m *QueryChecksumsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryChecksumsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryChecksumsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryChecksumsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryChecksumsResponse.Merge(m, src) +} +func (m *QueryChecksumsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryChecksumsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryChecksumsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryChecksumsResponse proto.InternalMessageInfo + +func (m *QueryChecksumsResponse) GetChecksums() []string { + if m != nil { + return m.Checksums + } + return nil +} + +func (m *QueryChecksumsResponse) GetPagination() *query.PageResponse { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryCodeRequest is the request type for the Query/Code RPC method. +type QueryCodeRequest struct { + // checksum is a hex encoded string of the code stored. + Checksum string `protobuf:"bytes,1,opt,name=checksum,proto3" json:"checksum,omitempty"` +} + +func (m *QueryCodeRequest) Reset() { *m = QueryCodeRequest{} } +func (m *QueryCodeRequest) String() string { return proto.CompactTextString(m) } +func (*QueryCodeRequest) ProtoMessage() {} +func (*QueryCodeRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_9e3718a8cb915777, []int{2} +} +func (m *QueryCodeRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryCodeRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryCodeRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryCodeRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryCodeRequest.Merge(m, src) +} +func (m *QueryCodeRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryCodeRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryCodeRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryCodeRequest proto.InternalMessageInfo + +func (m *QueryCodeRequest) GetChecksum() string { + if m != nil { + return m.Checksum + } + return "" +} + +// QueryCodeResponse is the response type for the Query/Code RPC method. +type QueryCodeResponse struct { + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` +} + +func (m *QueryCodeResponse) Reset() { *m = QueryCodeResponse{} } +func (m *QueryCodeResponse) String() string { return proto.CompactTextString(m) } +func (*QueryCodeResponse) ProtoMessage() {} +func (*QueryCodeResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_9e3718a8cb915777, []int{3} +} +func (m *QueryCodeResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryCodeResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryCodeResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryCodeResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryCodeResponse.Merge(m, src) +} +func (m *QueryCodeResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryCodeResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryCodeResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryCodeResponse proto.InternalMessageInfo + +func (m *QueryCodeResponse) GetData() []byte { + if m != nil { + return m.Data + } + return nil +} + +func init() { + proto.RegisterType((*QueryChecksumsRequest)(nil), "ibc.lightclients.wasm.v1.QueryChecksumsRequest") + proto.RegisterType((*QueryChecksumsResponse)(nil), "ibc.lightclients.wasm.v1.QueryChecksumsResponse") + proto.RegisterType((*QueryCodeRequest)(nil), "ibc.lightclients.wasm.v1.QueryCodeRequest") + proto.RegisterType((*QueryCodeResponse)(nil), "ibc.lightclients.wasm.v1.QueryCodeResponse") +} + +func init() { + proto.RegisterFile("ibc/lightclients/wasm/v1/query.proto", fileDescriptor_9e3718a8cb915777) +} + +var fileDescriptor_9e3718a8cb915777 = []byte{ + // 432 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xcf, 0xaa, 0xd3, 0x40, + 0x14, 0xc6, 0x3b, 0xf5, 0x2a, 0x66, 0x74, 0xa1, 0x03, 0x4a, 0x09, 0x97, 0x70, 0x89, 0x7f, 0xee, + 0xe5, 0x5e, 0x3a, 0xd3, 0xb4, 0x08, 0x82, 0xae, 0x14, 0x74, 0xab, 0x59, 0xb8, 0x70, 0x23, 0x93, + 0xc9, 0x90, 0x0e, 0x26, 0x99, 0xb4, 0x33, 0xa9, 0x14, 0x11, 0xc1, 0x27, 0x10, 0x5c, 0xea, 0xe3, + 0xb8, 0x70, 0x59, 0x70, 0xe3, 0x52, 0x5a, 0x1f, 0x44, 0x32, 0x93, 0x34, 0xad, 0x58, 0xda, 0xdd, + 0xe4, 0xf0, 0x3b, 0xdf, 0xf7, 0x9d, 0x93, 0x03, 0xef, 0x8a, 0x88, 0x91, 0x54, 0x24, 0x63, 0xcd, + 0x52, 0xc1, 0x73, 0xad, 0xc8, 0x3b, 0xaa, 0x32, 0x32, 0x0b, 0xc8, 0xa4, 0xe4, 0xd3, 0x39, 0x2e, + 0xa6, 0x52, 0x4b, 0xd4, 0x13, 0x11, 0xc3, 0x9b, 0x14, 0xae, 0x28, 0x3c, 0x0b, 0xdc, 0xe3, 0x44, + 0xca, 0x24, 0xe5, 0x84, 0x16, 0x82, 0xd0, 0x3c, 0x97, 0x9a, 0x6a, 0x21, 0x73, 0x65, 0xfb, 0xdc, + 0x73, 0x26, 0x55, 0x26, 0x15, 0x89, 0xa8, 0xe2, 0x56, 0x90, 0xcc, 0x82, 0x88, 0x6b, 0x1a, 0x90, + 0x82, 0x26, 0x22, 0x37, 0xb0, 0x65, 0xfd, 0x37, 0xf0, 0xd6, 0xcb, 0x8a, 0x78, 0x3a, 0xe6, 0xec, + 0xad, 0x2a, 0x33, 0x15, 0xf2, 0x49, 0xc9, 0x95, 0x46, 0xcf, 0x20, 0x6c, 0xe1, 0x1e, 0x38, 0x01, + 0x67, 0xd7, 0x86, 0xf7, 0xb1, 0x55, 0xc6, 0x95, 0x32, 0xb6, 0x51, 0x6b, 0x65, 0xfc, 0x82, 0x26, + 0xbc, 0xee, 0x0d, 0x37, 0x3a, 0xfd, 0x8f, 0xf0, 0xf6, 0xbf, 0x06, 0xaa, 0x90, 0xb9, 0xe2, 0xe8, + 0x18, 0x3a, 0xac, 0x29, 0xf6, 0xc0, 0xc9, 0xa5, 0x33, 0x27, 0x6c, 0x0b, 0xe8, 0xf9, 0x96, 0x7f, + 0xd7, 0xf8, 0x9f, 0xee, 0xf5, 0xb7, 0xd2, 0x5b, 0x01, 0x30, 0xbc, 0x61, 0x03, 0xc8, 0xb8, 0x09, + 0x88, 0x5c, 0x78, 0xb5, 0x71, 0x32, 0xa3, 0x39, 0xe1, 0xfa, 0xdb, 0x3f, 0x85, 0x37, 0x37, 0xf8, + 0x3a, 0x2b, 0x82, 0x47, 0x31, 0xd5, 0xd4, 0xc0, 0xd7, 0x43, 0xf3, 0x1e, 0x7e, 0xef, 0xc2, 0xcb, + 0x86, 0x44, 0x5f, 0x01, 0x74, 0xd6, 0xf3, 0x21, 0x82, 0x77, 0xfd, 0x37, 0xfc, 0xdf, 0x55, 0xbb, + 0x83, 0xc3, 0x1b, 0x6c, 0x1c, 0xff, 0xe2, 0xd3, 0xcf, 0x3f, 0x5f, 0xba, 0xf7, 0xd0, 0x1d, 0xb2, + 0xf3, 0x90, 0xda, 0x4d, 0x7e, 0x03, 0xf0, 0xa8, 0x1a, 0x06, 0x9d, 0xef, 0xf3, 0x69, 0x37, 0xe4, + 0x5e, 0x1c, 0xc4, 0xd6, 0x71, 0x1e, 0x99, 0x38, 0x0f, 0xd0, 0xe8, 0x80, 0x38, 0xe4, 0x7d, 0xf3, + 0xfc, 0x40, 0x98, 0x8c, 0xf9, 0x93, 0x57, 0x3f, 0x96, 0x1e, 0x58, 0x2c, 0x3d, 0xf0, 0x7b, 0xe9, + 0x81, 0xcf, 0x2b, 0xaf, 0xb3, 0x58, 0x79, 0x9d, 0x5f, 0x2b, 0xaf, 0xf3, 0xfa, 0x71, 0x22, 0xf4, + 0xb8, 0x8c, 0x30, 0x93, 0x19, 0xa9, 0x4f, 0x5a, 0x44, 0xac, 0x9f, 0x48, 0x92, 0xc9, 0xb8, 0x4c, + 0xb9, 0xb2, 0x56, 0xfd, 0xc6, 0x6b, 0xf0, 0xb0, 0x6f, 0xec, 0xf4, 0xbc, 0xe0, 0x2a, 0xba, 0x62, + 0x0e, 0x7c, 0xf4, 0x37, 0x00, 0x00, 0xff, 0xff, 0xbe, 0x82, 0x4a, 0xb3, 0x6c, 0x03, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// QueryClient is the client API for Query service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type QueryClient interface { + // Get all Wasm checksums + Checksums(ctx context.Context, in *QueryChecksumsRequest, opts ...grpc.CallOption) (*QueryChecksumsResponse, error) + // Get Wasm code for given checksum + Code(ctx context.Context, in *QueryCodeRequest, opts ...grpc.CallOption) (*QueryCodeResponse, error) +} + +type queryClient struct { + cc grpc1.ClientConn +} + +func NewQueryClient(cc grpc1.ClientConn) QueryClient { + return &queryClient{cc} +} + +func (c *queryClient) Checksums(ctx context.Context, in *QueryChecksumsRequest, opts ...grpc.CallOption) (*QueryChecksumsResponse, error) { + out := new(QueryChecksumsResponse) + err := c.cc.Invoke(ctx, "/ibc.lightclients.wasm.v1.Query/Checksums", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) Code(ctx context.Context, in *QueryCodeRequest, opts ...grpc.CallOption) (*QueryCodeResponse, error) { + out := new(QueryCodeResponse) + err := c.cc.Invoke(ctx, "/ibc.lightclients.wasm.v1.Query/Code", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QueryServer is the server API for Query service. +type QueryServer interface { + // Get all Wasm checksums + Checksums(context.Context, *QueryChecksumsRequest) (*QueryChecksumsResponse, error) + // Get Wasm code for given checksum + Code(context.Context, *QueryCodeRequest) (*QueryCodeResponse, error) +} + +// UnimplementedQueryServer can be embedded to have forward compatible implementations. +type UnimplementedQueryServer struct { +} + +func (*UnimplementedQueryServer) Checksums(ctx context.Context, req *QueryChecksumsRequest) (*QueryChecksumsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Checksums not implemented") +} +func (*UnimplementedQueryServer) Code(ctx context.Context, req *QueryCodeRequest) (*QueryCodeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Code not implemented") +} + +func RegisterQueryServer(s grpc1.Server, srv QueryServer) { + s.RegisterService(&_Query_serviceDesc, srv) +} + +func _Query_Checksums_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryChecksumsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Checksums(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.lightclients.wasm.v1.Query/Checksums", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Checksums(ctx, req.(*QueryChecksumsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_Code_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryCodeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Code(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.lightclients.wasm.v1.Query/Code", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Code(ctx, req.(*QueryCodeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Query_serviceDesc = grpc.ServiceDesc{ + ServiceName: "ibc.lightclients.wasm.v1.Query", + HandlerType: (*QueryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Checksums", + Handler: _Query_Checksums_Handler, + }, + { + MethodName: "Code", + Handler: _Query_Code_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "ibc/lightclients/wasm/v1/query.proto", +} + +func (m *QueryChecksumsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryChecksumsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryChecksumsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryChecksumsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryChecksumsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryChecksumsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Checksums) > 0 { + for iNdEx := len(m.Checksums) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Checksums[iNdEx]) + copy(dAtA[i:], m.Checksums[iNdEx]) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Checksums[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *QueryCodeRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryCodeRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryCodeRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Checksum) > 0 { + i -= len(m.Checksum) + copy(dAtA[i:], m.Checksum) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Checksum))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryCodeResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryCodeResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryCodeResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Data) > 0 { + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Data))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryChecksumsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryChecksumsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Checksums) > 0 { + for _, s := range m.Checksums { + l = len(s) + n += 1 + l + sovQuery(uint64(l)) + } + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryCodeRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Checksum) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryCodeResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Data) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *QueryChecksumsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryChecksumsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryChecksumsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageRequest{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryChecksumsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryChecksumsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryChecksumsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Checksums", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Checksums = append(m.Checksums, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageResponse{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryCodeRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryCodeRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryCodeRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Checksum", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Checksum = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryCodeResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryCodeResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryCodeResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...) + if m.Data == nil { + m.Data = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipQuery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/light-clients/08-wasm/types/query.pb.gw.go b/modules/light-clients/08-wasm/types/query.pb.gw.go new file mode 100644 index 00000000000..0b06f46b5f9 --- /dev/null +++ b/modules/light-clients/08-wasm/types/query.pb.gw.go @@ -0,0 +1,272 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: ibc/lightclients/wasm/v1/query.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage +var _ = metadata.Join + +var ( + filter_Query_Checksums_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_Query_Checksums_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryChecksumsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_Checksums_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Checksums(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Checksums_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryChecksumsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_Checksums_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.Checksums(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_Code_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryCodeRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["checksum"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "checksum") + } + + protoReq.Checksum, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "checksum", err) + } + + msg, err := client.Code(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Code_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryCodeRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["checksum"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "checksum") + } + + protoReq.Checksum, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "checksum", err) + } + + msg, err := server.Code(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterQueryHandlerServer registers the http handlers for service Query to "mux". +// UnaryRPC :call QueryServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. +func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { + + mux.Handle("GET", pattern_Query_Checksums_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Checksums_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Checksums_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Code_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Code_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Code_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterQueryHandler(ctx, mux, conn) +} + +// RegisterQueryHandler registers the http handlers for service Query to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) +} + +// RegisterQueryHandlerClient registers the http handlers for service Query +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "QueryClient" to call the correct interceptors. +func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { + + mux.Handle("GET", pattern_Query_Checksums_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Checksums_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Checksums_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Code_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Code_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Code_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Query_Checksums_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"ibc", "lightclients", "wasm", "v1", "checksums"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_Code_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6}, []string{"ibc", "lightclients", "wasm", "v1", "checksums", "checksum", "code"}, "", runtime.AssumeColonVerbOpt(false))) +) + +var ( + forward_Query_Checksums_0 = runtime.ForwardResponseMessage + + forward_Query_Code_0 = runtime.ForwardResponseMessage +) diff --git a/modules/light-clients/08-wasm/types/store.go b/modules/light-clients/08-wasm/types/store.go new file mode 100644 index 00000000000..897e050f5cc --- /dev/null +++ b/modules/light-clients/08-wasm/types/store.go @@ -0,0 +1,231 @@ +package types + +import ( + "bytes" + "errors" + "fmt" + "io" + "reflect" + "strings" + + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/store/cachekv" + storeprefix "cosmossdk.io/store/prefix" + "cosmossdk.io/store/tracekv" + storetypes "cosmossdk.io/store/types" +) + +var ( + _ wasmvmtypes.KVStore = &storeAdapter{} + _ storetypes.KVStore = &migrateClientWrappedStore{} + + subjectPrefix = []byte("subject/") + substitutePrefix = []byte("substitute/") +) + +// migrateClientWrappedStore combines two KVStores into one. +// +// Both stores are used for reads, but only the subjectStore is used for writes. For all operations, the key +// is checked to determine which store to use and must be prefixed with either "subject/" or "substitute/" accordingly. +// If the key is not prefixed with either "subject/" or "substitute/", a panic is thrown. +type migrateClientWrappedStore struct { + subjectStore storetypes.KVStore + substituteStore storetypes.KVStore +} + +func newMigrateClientWrappedStore(subjectStore, substituteStore storetypes.KVStore) migrateClientWrappedStore { + return migrateClientWrappedStore{ + subjectStore: subjectStore, + substituteStore: substituteStore, + } +} + +// Get implements the storetypes.KVStore interface. It allows reads from both the subjectStore and substituteStore. +// +// Get will panic if the key is not prefixed with either "subject/" or "substitute/". +func (ws migrateClientWrappedStore) Get(key []byte) []byte { + prefix, key := splitPrefix(key) + + return ws.getStore(prefix).Get(key) +} + +// Has implements the storetypes.KVStore interface. It allows reads from both the subjectStore and substituteStore. +// +// Note: contracts do not have access to the Has method, it is only implemented here to satisfy the storetypes.KVStore interface. +func (ws migrateClientWrappedStore) Has(key []byte) bool { + prefix, key := splitPrefix(key) + + return ws.getStore(prefix).Has(key) +} + +// Set implements the storetypes.KVStore interface. It allows writes solely to the subjectStore. +// +// Set will panic if the key is not prefixed with "subject/". +func (ws migrateClientWrappedStore) Set(key, value []byte) { + prefix, key := splitPrefix(key) + if !bytes.Equal(prefix, subjectPrefix) { + panic(fmt.Errorf("writes only allowed on subject store; key must be prefixed with \"%s\"", subjectPrefix)) + } + + ws.subjectStore.Set(key, value) +} + +// Delete implements the storetypes.KVStore interface. It allows deletions solely to the subjectStore. +// +// Delete will panic if the key is not prefixed with "subject/". +func (ws migrateClientWrappedStore) Delete(key []byte) { + prefix, key := splitPrefix(key) + if !bytes.Equal(prefix, subjectPrefix) { + panic(fmt.Errorf("writes only allowed on subject store; key must be prefixed with \"%s\"", subjectPrefix)) + } + + ws.subjectStore.Delete(key) +} + +// Iterator implements the storetypes.KVStore interface. It allows iteration over both the subjectStore and substituteStore. +// +// Iterator will panic if the start or end keys are not prefixed with either "subject/" or "substitute/". +func (ws migrateClientWrappedStore) Iterator(start, end []byte) storetypes.Iterator { + prefixStart, start := splitPrefix(start) + prefixEnd, end := splitPrefix(end) + + if !bytes.Equal(prefixStart, prefixEnd) { + panic(errors.New("start and end keys must be prefixed with the same prefix")) + } + + return ws.getStore(prefixStart).Iterator(start, end) +} + +// ReverseIterator implements the storetypes.KVStore interface. It allows iteration over both the subjectStore and substituteStore. +// +// ReverseIterator will panic if the start or end keys are not prefixed with either "subject/" or "substitute/". +func (ws migrateClientWrappedStore) ReverseIterator(start, end []byte) storetypes.Iterator { + prefixStart, start := splitPrefix(start) + prefixEnd, end := splitPrefix(end) + + if !bytes.Equal(prefixStart, prefixEnd) { + panic(errors.New("start and end keys must be prefixed with the same prefix")) + } + + return ws.getStore(prefixStart).ReverseIterator(start, end) +} + +// GetStoreType implements the storetypes.KVStore interface, it is implemented solely to satisfy the interface. +func (ws migrateClientWrappedStore) GetStoreType() storetypes.StoreType { + return ws.substituteStore.GetStoreType() +} + +// CacheWrap implements the storetypes.KVStore interface, it is implemented solely to satisfy the interface. +func (ws migrateClientWrappedStore) CacheWrap() storetypes.CacheWrap { + return cachekv.NewStore(ws) +} + +// CacheWrapWithTrace implements the storetypes.KVStore interface, it is implemented solely to satisfy the interface. +func (ws migrateClientWrappedStore) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { + return cachekv.NewStore(tracekv.NewStore(ws, w, tc)) +} + +// getStore returns the store to be used for the given key. If the key is prefixed with "subject/", the subjectStore +// is returned. If the key is prefixed with "substitute/", the substituteStore is returned. +// +// If the key is not prefixed with either "subject/" or "substitute/", a panic is thrown. +func (ws migrateClientWrappedStore) getStore(prefix []byte) storetypes.KVStore { + if bytes.Equal(prefix, subjectPrefix) { + return ws.subjectStore + } else if bytes.Equal(prefix, substitutePrefix) { + return ws.substituteStore + } + + panic(fmt.Errorf("key must be prefixed with either \"%s\" or \"%s\"", subjectPrefix, substitutePrefix)) +} + +// splitPrefix splits the key into the prefix and the key itself, if the key is prefixed with either "subject/" or "substitute/". +// If the key is not prefixed with either "subject/" or "substitute/", the prefix is nil. +func splitPrefix(key []byte) ([]byte, []byte) { + if bytes.HasPrefix(key, subjectPrefix) { + return subjectPrefix, bytes.TrimPrefix(key, subjectPrefix) + } else if bytes.HasPrefix(key, substitutePrefix) { + return substitutePrefix, bytes.TrimPrefix(key, substitutePrefix) + } + + return nil, key +} + +// storeAdapter bridges the SDK store implementation to wasmvm one. It implements the wasmvmtypes.KVStore interface. +type storeAdapter struct { + parent storetypes.KVStore +} + +// newStoreAdapter constructor +func newStoreAdapter(s storetypes.KVStore) *storeAdapter { + if s == nil { + panic(errors.New("store must not be nil")) + } + return &storeAdapter{parent: s} +} + +func (s storeAdapter) Get(key []byte) []byte { + return s.parent.Get(key) +} + +func (s storeAdapter) Set(key, value []byte) { + s.parent.Set(key, value) +} + +func (s storeAdapter) Delete(key []byte) { + s.parent.Delete(key) +} + +func (s storeAdapter) Iterator(start, end []byte) wasmvmtypes.Iterator { + return s.parent.Iterator(start, end) +} + +func (s storeAdapter) ReverseIterator(start, end []byte) wasmvmtypes.Iterator { + return s.parent.ReverseIterator(start, end) +} + +// getClientID extracts and validates the clientID from the clientStore's prefix. +// +// Due to the 02-client module not passing the clientID to the 08-wasm module, +// this function was devised to infer it from the store's prefix. +// The expected format of the clientStore prefix is "/{clientID}/". +// If the clientStore is of type migrateProposalWrappedStore, the subjectStore's prefix is utilized instead. +func getClientID(clientStore storetypes.KVStore) (string, error) { + upws, isMigrateProposalWrappedStore := clientStore.(migrateClientWrappedStore) + if isMigrateProposalWrappedStore { + // if the clientStore is a migrateProposalWrappedStore, we retrieve the subjectStore + // because the contract call will be made on the client with the ID of the subjectStore + clientStore = upws.subjectStore + } + + store, ok := clientStore.(storeprefix.Store) + if !ok { + return "", errorsmod.Wrap(ErrRetrieveClientID, "clientStore is not a prefix store") + } + + // using reflect to retrieve the private prefix field + r := reflect.ValueOf(&store).Elem() + + f := r.FieldByName("prefix") + if !f.IsValid() { + return "", errorsmod.Wrap(ErrRetrieveClientID, "prefix field not found") + } + + prefix := string(f.Bytes()) + + split := strings.Split(prefix, "/") + if len(split) < 3 { + return "", errorsmod.Wrap(ErrRetrieveClientID, "prefix is not of the expected form") + } + + // the clientID is the second to last element of the prefix + // the prefix is expected to be of the form "/{clientID}/" + clientID := split[len(split)-2] + if err := ValidateClientID(clientID); err != nil { + return "", errorsmod.Wrapf(ErrRetrieveClientID, "prefix does not contain a valid clientID: %s", err.Error()) + } + + return clientID, nil +} diff --git a/modules/light-clients/08-wasm/types/store_test.go b/modules/light-clients/08-wasm/types/store_test.go new file mode 100644 index 00000000000..57261d6655f --- /dev/null +++ b/modules/light-clients/08-wasm/types/store_test.go @@ -0,0 +1,417 @@ +package types_test + +import ( + "errors" + fmt "fmt" + + prefixstore "cosmossdk.io/store/prefix" + storetypes "cosmossdk.io/store/types" + + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" +) + +var invalidPrefix = []byte("invalid/") + +// TestMigrateClientWrappedStoreGetStore tests the getStore method of the migrateClientWrappedStore. +func (suite *TypesTestSuite) TestMigrateClientWrappedStoreGetStore() { + // calls suite.SetupWasmWithMockVM() and creates two clients with their respective stores + subjectStore, substituteStore := suite.GetSubjectAndSubstituteStore() + + testCases := []struct { + name string + prefix []byte + expStore storetypes.KVStore + expPanic error + }{ + { + "success: subject store", + types.SubjectPrefix, + subjectStore, + nil, + }, + { + "success: substitute store", + types.SubstitutePrefix, + substituteStore, + nil, + }, + { + "failure: invalid prefix", + invalidPrefix, + nil, + fmt.Errorf("key must be prefixed with either \"%s\" or \"%s\"", types.SubjectPrefix, types.SubstitutePrefix), + }, + { + "failure: invalid prefix contains both subject/ and substitute/", + append(types.SubjectPrefix, types.SubstitutePrefix...), + nil, + fmt.Errorf("key must be prefixed with either \"%s\" or \"%s\"", types.SubjectPrefix, types.SubstitutePrefix), + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + wrappedStore := types.NewMigrateProposalWrappedStore(subjectStore, substituteStore) + + if tc.expPanic == nil { + suite.Require().Equal(tc.expStore, wrappedStore.GetStore(tc.prefix)) + } else { + suite.Require().PanicsWithError(tc.expPanic.Error(), func() { wrappedStore.GetStore(tc.prefix) }) + } + }) + } +} + +// TestSplitPrefix tests the splitPrefix function. +func (suite *TypesTestSuite) TestSplitPrefix() { + clientStateKey := host.ClientStateKey() + + testCases := []struct { + name string + prefix []byte + expValues [][]byte + }{ + { + "success: subject prefix", + append(types.SubjectPrefix, clientStateKey...), + [][]byte{types.SubjectPrefix, clientStateKey}, + }, + { + "success: substitute prefix", + append(types.SubstitutePrefix, clientStateKey...), + [][]byte{types.SubstitutePrefix, clientStateKey}, + }, + { + "success: nil prefix returned", + invalidPrefix, + [][]byte{nil, invalidPrefix}, + }, + { + "success: invalid prefix contains both subject/ and substitute/", + append(types.SubjectPrefix, types.SubstitutePrefix...), + [][]byte{types.SubjectPrefix, types.SubstitutePrefix}, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + prefix, key := types.SplitPrefix(tc.prefix) + + suite.Require().Equal(tc.expValues[0], prefix) + suite.Require().Equal(tc.expValues[1], key) + }) + } +} + +// TestMigrateClientWrappedStoreGet tests the Get method of the migrateClientWrappedStore. +func (suite *TypesTestSuite) TestMigrateClientWrappedStoreGet() { + // calls suite.SetupWasmWithMockVM() and creates two clients with their respective stores + subjectStore, substituteStore := suite.GetSubjectAndSubstituteStore() + + testCases := []struct { + name string + prefix []byte + key []byte + expStore storetypes.KVStore + expPanic error + }{ + { + "success: subject store Get", + types.SubjectPrefix, + host.ClientStateKey(), + subjectStore, + nil, + }, + { + "success: substitute store Get", + types.SubstitutePrefix, + host.ClientStateKey(), + substituteStore, + nil, + }, + { + "failure: key not prefixed with subject/ or substitute/", + invalidPrefix, + host.ClientStateKey(), + nil, + fmt.Errorf("key must be prefixed with either \"%s\" or \"%s\"", types.SubjectPrefix, types.SubstitutePrefix), + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + wrappedStore := types.NewMigrateProposalWrappedStore(subjectStore, substituteStore) + + prefixedKey := tc.prefix + prefixedKey = append(prefixedKey, tc.key...) + + if tc.expPanic == nil { + expValue := tc.expStore.Get(tc.key) + + suite.Require().Equal(expValue, wrappedStore.Get(prefixedKey)) + } else { + suite.Require().PanicsWithError(tc.expPanic.Error(), func() { wrappedStore.Get(prefixedKey) }) + } + }) + } +} + +// TestMigrateClientWrappedStoreSet tests the Set method of the migrateClientWrappedStore. +func (suite *TypesTestSuite) TestMigrateClientWrappedStoreSet() { + // calls suite.SetupWasmWithMockVM() and creates two clients with their respective stores + subjectStore, substituteStore := suite.GetSubjectAndSubstituteStore() + + testCases := []struct { + name string + prefix []byte + key []byte + expStore storetypes.KVStore + expPanic error + }{ + { + "success: subject store Set", + types.SubjectPrefix, + host.ClientStateKey(), + subjectStore, + nil, + }, + { + "failure: cannot Set on substitute store", + types.SubstitutePrefix, + host.ClientStateKey(), + nil, + fmt.Errorf("writes only allowed on subject store; key must be prefixed with \"%s\"", types.SubjectPrefix), + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + wrappedStore := types.NewMigrateProposalWrappedStore(subjectStore, substituteStore) + + prefixedKey := tc.prefix + prefixedKey = append(prefixedKey, tc.key...) + + if tc.expPanic == nil { + wrappedStore.Set(prefixedKey, wasmtesting.MockClientStateBz) + + expValue := tc.expStore.Get(tc.key) + + suite.Require().Equal(expValue, wasmtesting.MockClientStateBz) + } else { + suite.Require().PanicsWithError(tc.expPanic.Error(), func() { wrappedStore.Set(prefixedKey, wasmtesting.MockClientStateBz) }) + } + }) + } +} + +// TestMigrateClientWrappedStoreDelete tests the Delete method of the migrateClientWrappedStore. +func (suite *TypesTestSuite) TestMigrateClientWrappedStoreDelete() { + // calls suite.SetupWasmWithMockVM() and creates two clients with their respective stores + subjectStore, substituteStore := suite.GetSubjectAndSubstituteStore() + + testCases := []struct { + name string + prefix []byte + key []byte + expStore storetypes.KVStore + expPanic error + }{ + { + "success: subject store Delete", + types.SubjectPrefix, + host.ClientStateKey(), + subjectStore, + nil, + }, + { + "failure: cannot Delete on substitute store", + types.SubstitutePrefix, + host.ClientStateKey(), + nil, + fmt.Errorf("writes only allowed on subject store; key must be prefixed with \"%s\"", types.SubjectPrefix), + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + wrappedStore := types.NewMigrateProposalWrappedStore(subjectStore, substituteStore) + + prefixedKey := tc.prefix + prefixedKey = append(prefixedKey, tc.key...) + + if tc.expPanic == nil { + wrappedStore.Delete(prefixedKey) + + suite.Require().False(tc.expStore.Has(tc.key)) + } else { + suite.Require().PanicsWithError(tc.expPanic.Error(), func() { wrappedStore.Delete(prefixedKey) }) + } + }) + } +} + +// TestMigrateClientWrappedStoreIterators tests the Iterator/ReverseIterator methods of the migrateClientWrappedStore. +func (suite *TypesTestSuite) TestMigrateClientWrappedStoreIterators() { + // calls suite.SetupWasmWithMockVM() and creates two clients with their respective stores + subjectStore, substituteStore := suite.GetSubjectAndSubstituteStore() + + testCases := []struct { + name string + prefixStart []byte + prefixEnd []byte + start []byte + end []byte + expPanic error + }{ + { + "success: subject store Iterate", + types.SubjectPrefix, + types.SubjectPrefix, + []byte("start"), + []byte("end"), + nil, + }, + { + "success: substitute store Iterate", + types.SubstitutePrefix, + types.SubstitutePrefix, + []byte("start"), + []byte("end"), + nil, + }, + { + "failure: key not prefixed", + invalidPrefix, + invalidPrefix, + []byte("start"), + []byte("end"), + fmt.Errorf("key must be prefixed with either \"%s\" or \"%s\"", types.SubjectPrefix, types.SubstitutePrefix), + }, + { + "failure: start and end keys not prefixed with same prefix", + types.SubjectPrefix, + types.SubstitutePrefix, + []byte("start"), + []byte("end"), + errors.New("start and end keys must be prefixed with the same prefix"), + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + wrappedStore := types.NewMigrateProposalWrappedStore(subjectStore, substituteStore) + + prefixedKeyStart := tc.prefixStart + prefixedKeyStart = append(prefixedKeyStart, tc.start...) + prefixedKeyEnd := tc.prefixEnd + prefixedKeyEnd = append(prefixedKeyEnd, tc.end...) + + if tc.expPanic == nil { + suite.Require().NotNil(wrappedStore.Iterator(prefixedKeyStart, prefixedKeyEnd)) + suite.Require().NotNil(wrappedStore.ReverseIterator(prefixedKeyStart, prefixedKeyEnd)) + } else { + suite.Require().PanicsWithError(tc.expPanic.Error(), func() { wrappedStore.Iterator(prefixedKeyStart, prefixedKeyEnd) }) + suite.Require().PanicsWithError(tc.expPanic.Error(), func() { wrappedStore.ReverseIterator(prefixedKeyStart, prefixedKeyEnd) }) + } + }) + } +} + +func (suite *TypesTestSuite) TestGetClientID() { + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), defaultWasmClientID) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success: clientID retrieved", + func() {}, + nil, + }, + { + "success: clientID retrieved from migrateClientWrappedStore", + func() { + clientStore = types.NewMigrateProposalWrappedStore(clientStore, nil) + }, + nil, + }, + { + "failure: clientStore is nil", + func() { + clientStore = nil + }, + types.ErrRetrieveClientID, + }, + { + "failure: prefix store does not contain prefix", + func() { + clientStore = prefixstore.NewStore(nil, nil) + }, + types.ErrRetrieveClientID, + }, + { + "failure: prefix does not contain slash separated path", + func() { + clientStore = prefixstore.NewStore(nil, []byte("not-a-slash-separated-path")) + }, + types.ErrRetrieveClientID, + }, + { + "failure: prefix only contains one slash", + func() { + clientStore = prefixstore.NewStore(nil, []byte("only-one-slash/")) + }, + types.ErrRetrieveClientID, + }, + { + "failure: prefix does not contain a wasm clientID", + func() { + clientStore = prefixstore.NewStore(nil, []byte("/not-client-id/")) + }, + types.ErrRetrieveClientID, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + tc.malleate() + clientID, err := types.GetClientID(clientStore) + + if tc.expError == nil { + suite.Require().NoError(err) + suite.Require().Equal(defaultWasmClientID, clientID) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} + +// GetSubjectAndSubstituteStore returns a subject and substitute store for testing. +func (suite *TypesTestSuite) GetSubjectAndSubstituteStore() (storetypes.KVStore, storetypes.KVStore) { + suite.SetupWasmWithMockVM() + + endpointA := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpointA.CreateClient() + suite.Require().NoError(err) + + subjectClientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpointA.ClientID) + + substituteEndpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err = substituteEndpoint.CreateClient() + suite.Require().NoError(err) + + substituteClientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), substituteEndpoint.ClientID) + + return subjectClientStore, substituteClientStore +} diff --git a/modules/light-clients/08-wasm/types/tx.pb.go b/modules/light-clients/08-wasm/types/tx.pb.go new file mode 100644 index 00000000000..8c0fdf5f1f0 --- /dev/null +++ b/modules/light-clients/08-wasm/types/tx.pb.go @@ -0,0 +1,1524 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/lightclients/wasm/v1/tx.proto + +package types + +import ( + context "context" + fmt "fmt" + _ "github.com/cosmos/cosmos-sdk/types/msgservice" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// MsgStoreCode defines the request type for the StoreCode rpc. +type MsgStoreCode struct { + // signer address + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // wasm byte code of light client contract. It can be raw or gzip compressed + WasmByteCode []byte `protobuf:"bytes,2,opt,name=wasm_byte_code,json=wasmByteCode,proto3" json:"wasm_byte_code,omitempty"` +} + +func (m *MsgStoreCode) Reset() { *m = MsgStoreCode{} } +func (m *MsgStoreCode) String() string { return proto.CompactTextString(m) } +func (*MsgStoreCode) ProtoMessage() {} +func (*MsgStoreCode) Descriptor() ([]byte, []int) { + return fileDescriptor_1d9737363bf1e38d, []int{0} +} +func (m *MsgStoreCode) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgStoreCode) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgStoreCode.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgStoreCode) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgStoreCode.Merge(m, src) +} +func (m *MsgStoreCode) XXX_Size() int { + return m.Size() +} +func (m *MsgStoreCode) XXX_DiscardUnknown() { + xxx_messageInfo_MsgStoreCode.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgStoreCode proto.InternalMessageInfo + +func (m *MsgStoreCode) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +func (m *MsgStoreCode) GetWasmByteCode() []byte { + if m != nil { + return m.WasmByteCode + } + return nil +} + +// MsgStoreCodeResponse defines the response type for the StoreCode rpc +type MsgStoreCodeResponse struct { + // checksum is the sha256 hash of the stored code + Checksum []byte `protobuf:"bytes,1,opt,name=checksum,proto3" json:"checksum,omitempty"` +} + +func (m *MsgStoreCodeResponse) Reset() { *m = MsgStoreCodeResponse{} } +func (m *MsgStoreCodeResponse) String() string { return proto.CompactTextString(m) } +func (*MsgStoreCodeResponse) ProtoMessage() {} +func (*MsgStoreCodeResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_1d9737363bf1e38d, []int{1} +} +func (m *MsgStoreCodeResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgStoreCodeResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgStoreCodeResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgStoreCodeResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgStoreCodeResponse.Merge(m, src) +} +func (m *MsgStoreCodeResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgStoreCodeResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgStoreCodeResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgStoreCodeResponse proto.InternalMessageInfo + +func (m *MsgStoreCodeResponse) GetChecksum() []byte { + if m != nil { + return m.Checksum + } + return nil +} + +// MsgRemoveChecksum defines the request type for the MsgRemoveChecksum rpc. +type MsgRemoveChecksum struct { + // signer address + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // checksum is the sha256 hash to be removed from the store + Checksum []byte `protobuf:"bytes,2,opt,name=checksum,proto3" json:"checksum,omitempty"` +} + +func (m *MsgRemoveChecksum) Reset() { *m = MsgRemoveChecksum{} } +func (m *MsgRemoveChecksum) String() string { return proto.CompactTextString(m) } +func (*MsgRemoveChecksum) ProtoMessage() {} +func (*MsgRemoveChecksum) Descriptor() ([]byte, []int) { + return fileDescriptor_1d9737363bf1e38d, []int{2} +} +func (m *MsgRemoveChecksum) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgRemoveChecksum) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgRemoveChecksum.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgRemoveChecksum) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgRemoveChecksum.Merge(m, src) +} +func (m *MsgRemoveChecksum) XXX_Size() int { + return m.Size() +} +func (m *MsgRemoveChecksum) XXX_DiscardUnknown() { + xxx_messageInfo_MsgRemoveChecksum.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgRemoveChecksum proto.InternalMessageInfo + +func (m *MsgRemoveChecksum) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +func (m *MsgRemoveChecksum) GetChecksum() []byte { + if m != nil { + return m.Checksum + } + return nil +} + +// MsgStoreChecksumResponse defines the response type for the StoreCode rpc +type MsgRemoveChecksumResponse struct { +} + +func (m *MsgRemoveChecksumResponse) Reset() { *m = MsgRemoveChecksumResponse{} } +func (m *MsgRemoveChecksumResponse) String() string { return proto.CompactTextString(m) } +func (*MsgRemoveChecksumResponse) ProtoMessage() {} +func (*MsgRemoveChecksumResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_1d9737363bf1e38d, []int{3} +} +func (m *MsgRemoveChecksumResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgRemoveChecksumResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgRemoveChecksumResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgRemoveChecksumResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgRemoveChecksumResponse.Merge(m, src) +} +func (m *MsgRemoveChecksumResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgRemoveChecksumResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgRemoveChecksumResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgRemoveChecksumResponse proto.InternalMessageInfo + +// MsgMigrateContract defines the request type for the MigrateContract rpc. +type MsgMigrateContract struct { + // signer address + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // the client id of the contract + ClientId string `protobuf:"bytes,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // checksum is the sha256 hash of the new wasm byte code for the contract + Checksum []byte `protobuf:"bytes,3,opt,name=checksum,proto3" json:"checksum,omitempty"` + // the json encoded message to be passed to the contract on migration + Msg []byte `protobuf:"bytes,4,opt,name=msg,proto3" json:"msg,omitempty"` +} + +func (m *MsgMigrateContract) Reset() { *m = MsgMigrateContract{} } +func (m *MsgMigrateContract) String() string { return proto.CompactTextString(m) } +func (*MsgMigrateContract) ProtoMessage() {} +func (*MsgMigrateContract) Descriptor() ([]byte, []int) { + return fileDescriptor_1d9737363bf1e38d, []int{4} +} +func (m *MsgMigrateContract) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgMigrateContract) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgMigrateContract.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgMigrateContract) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgMigrateContract.Merge(m, src) +} +func (m *MsgMigrateContract) XXX_Size() int { + return m.Size() +} +func (m *MsgMigrateContract) XXX_DiscardUnknown() { + xxx_messageInfo_MsgMigrateContract.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgMigrateContract proto.InternalMessageInfo + +func (m *MsgMigrateContract) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +func (m *MsgMigrateContract) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +func (m *MsgMigrateContract) GetChecksum() []byte { + if m != nil { + return m.Checksum + } + return nil +} + +func (m *MsgMigrateContract) GetMsg() []byte { + if m != nil { + return m.Msg + } + return nil +} + +// MsgMigrateContractResponse defines the response type for the MigrateContract rpc +type MsgMigrateContractResponse struct { +} + +func (m *MsgMigrateContractResponse) Reset() { *m = MsgMigrateContractResponse{} } +func (m *MsgMigrateContractResponse) String() string { return proto.CompactTextString(m) } +func (*MsgMigrateContractResponse) ProtoMessage() {} +func (*MsgMigrateContractResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_1d9737363bf1e38d, []int{5} +} +func (m *MsgMigrateContractResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgMigrateContractResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgMigrateContractResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgMigrateContractResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgMigrateContractResponse.Merge(m, src) +} +func (m *MsgMigrateContractResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgMigrateContractResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgMigrateContractResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgMigrateContractResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*MsgStoreCode)(nil), "ibc.lightclients.wasm.v1.MsgStoreCode") + proto.RegisterType((*MsgStoreCodeResponse)(nil), "ibc.lightclients.wasm.v1.MsgStoreCodeResponse") + proto.RegisterType((*MsgRemoveChecksum)(nil), "ibc.lightclients.wasm.v1.MsgRemoveChecksum") + proto.RegisterType((*MsgRemoveChecksumResponse)(nil), "ibc.lightclients.wasm.v1.MsgRemoveChecksumResponse") + proto.RegisterType((*MsgMigrateContract)(nil), "ibc.lightclients.wasm.v1.MsgMigrateContract") + proto.RegisterType((*MsgMigrateContractResponse)(nil), "ibc.lightclients.wasm.v1.MsgMigrateContractResponse") +} + +func init() { proto.RegisterFile("ibc/lightclients/wasm/v1/tx.proto", fileDescriptor_1d9737363bf1e38d) } + +var fileDescriptor_1d9737363bf1e38d = []byte{ + // 445 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x53, 0x4d, 0x6f, 0xd3, 0x40, + 0x14, 0xcc, 0x26, 0x50, 0x35, 0x8f, 0xa8, 0x80, 0x85, 0x20, 0xb8, 0xc8, 0x2a, 0x11, 0x42, 0x55, + 0x21, 0x6b, 0xda, 0x72, 0x40, 0x88, 0x53, 0x7b, 0xe2, 0xe0, 0x8b, 0x41, 0x48, 0x70, 0x89, 0xe2, + 0xf5, 0x6a, 0xb3, 0x22, 0xeb, 0x8d, 0xfc, 0xd6, 0x81, 0xdc, 0x10, 0xe2, 0x07, 0xf0, 0x53, 0xfa, + 0x33, 0x38, 0xf6, 0xc8, 0x81, 0x03, 0x4a, 0x0e, 0xfd, 0x1b, 0xc8, 0x5f, 0xc1, 0x4e, 0x95, 0xaa, + 0xb9, 0x79, 0x9f, 0xe6, 0xcd, 0x8c, 0xdf, 0x68, 0xe0, 0xb1, 0x0c, 0x98, 0x3b, 0x96, 0x62, 0x64, + 0xd8, 0x58, 0xf2, 0xc8, 0xa0, 0xfb, 0x65, 0x88, 0xca, 0x9d, 0x1e, 0xba, 0xe6, 0x2b, 0x9d, 0xc4, + 0xda, 0x68, 0xab, 0x2b, 0x03, 0x46, 0xab, 0x10, 0x9a, 0x42, 0xe8, 0xf4, 0xd0, 0x7e, 0xc0, 0x34, + 0x2a, 0x8d, 0xae, 0x42, 0x91, 0x6e, 0x28, 0x14, 0xf9, 0x4a, 0xef, 0x23, 0x74, 0x3c, 0x14, 0xef, + 0x8c, 0x8e, 0xf9, 0xa9, 0x0e, 0xb9, 0x75, 0x1f, 0xb6, 0x50, 0x8a, 0x88, 0xc7, 0x5d, 0xb2, 0x47, + 0xf6, 0xdb, 0x7e, 0xf1, 0xb2, 0x9e, 0xc0, 0x4e, 0xca, 0x35, 0x08, 0x66, 0x86, 0x0f, 0x98, 0x0e, + 0x79, 0xb7, 0xb9, 0x47, 0xf6, 0x3b, 0x7e, 0x27, 0x9d, 0x9e, 0xcc, 0x4c, 0xb6, 0xfd, 0xfa, 0xd6, + 0xf7, 0x8b, 0xb3, 0x83, 0x62, 0xa5, 0x77, 0x04, 0xf7, 0xaa, 0xd4, 0x3e, 0xc7, 0x89, 0x8e, 0x90, + 0x5b, 0x36, 0x6c, 0xb3, 0x11, 0x67, 0x9f, 0x31, 0x51, 0x99, 0x48, 0xc7, 0x5f, 0xbe, 0x7b, 0xef, + 0xe1, 0xae, 0x87, 0xc2, 0xe7, 0x4a, 0x4f, 0xf9, 0x69, 0x31, 0x5c, 0xeb, 0xa9, 0x4a, 0xd4, 0xac, + 0x13, 0xd5, 0x9d, 0xec, 0xc2, 0xc3, 0x4b, 0xac, 0xa5, 0x9d, 0xde, 0x0f, 0x02, 0x96, 0x87, 0xc2, + 0x93, 0x22, 0x1e, 0xa6, 0xbf, 0x11, 0x99, 0x78, 0xc8, 0xcc, 0x5a, 0xd1, 0x5d, 0x68, 0xe7, 0xc7, + 0x1d, 0xc8, 0x30, 0x53, 0x6d, 0xfb, 0xdb, 0xf9, 0xe0, 0x6d, 0x58, 0x73, 0xd4, 0xaa, 0x3b, 0xb2, + 0xee, 0x40, 0x4b, 0xa1, 0xe8, 0xde, 0xc8, 0xc6, 0xe9, 0x67, 0xdd, 0xe3, 0x23, 0xb0, 0x2f, 0xbb, + 0x28, 0x4d, 0x1e, 0xfd, 0x69, 0x42, 0xcb, 0x43, 0x61, 0x31, 0x68, 0xff, 0xcf, 0xea, 0x29, 0x5d, + 0x97, 0x37, 0xad, 0x1e, 0xde, 0xa6, 0xd7, 0xc3, 0x2d, 0x03, 0x8a, 0x61, 0x67, 0x25, 0x81, 0x67, + 0x57, 0x32, 0xd4, 0xc1, 0xf6, 0xf1, 0x06, 0xe0, 0xa5, 0x66, 0x02, 0xb7, 0x57, 0x13, 0x78, 0x7e, + 0x25, 0xcf, 0x0a, 0xda, 0x7e, 0xb9, 0x09, 0xba, 0x94, 0xb5, 0x6f, 0x7e, 0xbb, 0x38, 0x3b, 0x20, + 0x27, 0x1f, 0x7e, 0xcd, 0x1d, 0x72, 0x3e, 0x77, 0xc8, 0xdf, 0xb9, 0x43, 0x7e, 0x2e, 0x9c, 0xc6, + 0xf9, 0xc2, 0x69, 0xfc, 0x5e, 0x38, 0x8d, 0x4f, 0x6f, 0x84, 0x34, 0xa3, 0x24, 0xa0, 0x4c, 0x2b, + 0xb7, 0xe8, 0x90, 0x0c, 0x58, 0x5f, 0x68, 0x57, 0xe9, 0x30, 0x19, 0x73, 0xcc, 0x2b, 0xd9, 0x2f, + 0x3b, 0xf9, 0xe2, 0x55, 0x3f, 0xab, 0xa5, 0x99, 0x4d, 0x38, 0x06, 0x5b, 0x59, 0xc9, 0x8e, 0xff, + 0x05, 0x00, 0x00, 0xff, 0xff, 0x5f, 0xb7, 0xbc, 0xcb, 0xbc, 0x03, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// MsgClient is the client API for Msg service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type MsgClient interface { + // StoreCode defines a rpc handler method for MsgStoreCode. + StoreCode(ctx context.Context, in *MsgStoreCode, opts ...grpc.CallOption) (*MsgStoreCodeResponse, error) + // RemoveChecksum defines a rpc handler method for MsgRemoveChecksum. + RemoveChecksum(ctx context.Context, in *MsgRemoveChecksum, opts ...grpc.CallOption) (*MsgRemoveChecksumResponse, error) + // MigrateContract defines a rpc handler method for MsgMigrateContract. + MigrateContract(ctx context.Context, in *MsgMigrateContract, opts ...grpc.CallOption) (*MsgMigrateContractResponse, error) +} + +type msgClient struct { + cc grpc1.ClientConn +} + +func NewMsgClient(cc grpc1.ClientConn) MsgClient { + return &msgClient{cc} +} + +func (c *msgClient) StoreCode(ctx context.Context, in *MsgStoreCode, opts ...grpc.CallOption) (*MsgStoreCodeResponse, error) { + out := new(MsgStoreCodeResponse) + err := c.cc.Invoke(ctx, "/ibc.lightclients.wasm.v1.Msg/StoreCode", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) RemoveChecksum(ctx context.Context, in *MsgRemoveChecksum, opts ...grpc.CallOption) (*MsgRemoveChecksumResponse, error) { + out := new(MsgRemoveChecksumResponse) + err := c.cc.Invoke(ctx, "/ibc.lightclients.wasm.v1.Msg/RemoveChecksum", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) MigrateContract(ctx context.Context, in *MsgMigrateContract, opts ...grpc.CallOption) (*MsgMigrateContractResponse, error) { + out := new(MsgMigrateContractResponse) + err := c.cc.Invoke(ctx, "/ibc.lightclients.wasm.v1.Msg/MigrateContract", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MsgServer is the server API for Msg service. +type MsgServer interface { + // StoreCode defines a rpc handler method for MsgStoreCode. + StoreCode(context.Context, *MsgStoreCode) (*MsgStoreCodeResponse, error) + // RemoveChecksum defines a rpc handler method for MsgRemoveChecksum. + RemoveChecksum(context.Context, *MsgRemoveChecksum) (*MsgRemoveChecksumResponse, error) + // MigrateContract defines a rpc handler method for MsgMigrateContract. + MigrateContract(context.Context, *MsgMigrateContract) (*MsgMigrateContractResponse, error) +} + +// UnimplementedMsgServer can be embedded to have forward compatible implementations. +type UnimplementedMsgServer struct { +} + +func (*UnimplementedMsgServer) StoreCode(ctx context.Context, req *MsgStoreCode) (*MsgStoreCodeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method StoreCode not implemented") +} +func (*UnimplementedMsgServer) RemoveChecksum(ctx context.Context, req *MsgRemoveChecksum) (*MsgRemoveChecksumResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RemoveChecksum not implemented") +} +func (*UnimplementedMsgServer) MigrateContract(ctx context.Context, req *MsgMigrateContract) (*MsgMigrateContractResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method MigrateContract not implemented") +} + +func RegisterMsgServer(s grpc1.Server, srv MsgServer) { + s.RegisterService(&_Msg_serviceDesc, srv) +} + +func _Msg_StoreCode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgStoreCode) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).StoreCode(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.lightclients.wasm.v1.Msg/StoreCode", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).StoreCode(ctx, req.(*MsgStoreCode)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_RemoveChecksum_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgRemoveChecksum) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).RemoveChecksum(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.lightclients.wasm.v1.Msg/RemoveChecksum", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).RemoveChecksum(ctx, req.(*MsgRemoveChecksum)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_MigrateContract_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgMigrateContract) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).MigrateContract(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.lightclients.wasm.v1.Msg/MigrateContract", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).MigrateContract(ctx, req.(*MsgMigrateContract)) + } + return interceptor(ctx, in, info, handler) +} + +var _Msg_serviceDesc = grpc.ServiceDesc{ + ServiceName: "ibc.lightclients.wasm.v1.Msg", + HandlerType: (*MsgServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "StoreCode", + Handler: _Msg_StoreCode_Handler, + }, + { + MethodName: "RemoveChecksum", + Handler: _Msg_RemoveChecksum_Handler, + }, + { + MethodName: "MigrateContract", + Handler: _Msg_MigrateContract_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "ibc/lightclients/wasm/v1/tx.proto", +} + +func (m *MsgStoreCode) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgStoreCode) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgStoreCode) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.WasmByteCode) > 0 { + i -= len(m.WasmByteCode) + copy(dAtA[i:], m.WasmByteCode) + i = encodeVarintTx(dAtA, i, uint64(len(m.WasmByteCode))) + i-- + dAtA[i] = 0x12 + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgStoreCodeResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgStoreCodeResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgStoreCodeResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Checksum) > 0 { + i -= len(m.Checksum) + copy(dAtA[i:], m.Checksum) + i = encodeVarintTx(dAtA, i, uint64(len(m.Checksum))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgRemoveChecksum) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgRemoveChecksum) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgRemoveChecksum) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Checksum) > 0 { + i -= len(m.Checksum) + copy(dAtA[i:], m.Checksum) + i = encodeVarintTx(dAtA, i, uint64(len(m.Checksum))) + i-- + dAtA[i] = 0x12 + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgRemoveChecksumResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgRemoveChecksumResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgRemoveChecksumResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgMigrateContract) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgMigrateContract) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgMigrateContract) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Msg) > 0 { + i -= len(m.Msg) + copy(dAtA[i:], m.Msg) + i = encodeVarintTx(dAtA, i, uint64(len(m.Msg))) + i-- + dAtA[i] = 0x22 + } + if len(m.Checksum) > 0 { + i -= len(m.Checksum) + copy(dAtA[i:], m.Checksum) + i = encodeVarintTx(dAtA, i, uint64(len(m.Checksum))) + i-- + dAtA[i] = 0x1a + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0x12 + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgMigrateContractResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgMigrateContractResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgMigrateContractResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgStoreCode) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.WasmByteCode) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgStoreCodeResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Checksum) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgRemoveChecksum) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Checksum) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgRemoveChecksumResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgMigrateContract) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Checksum) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Msg) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgMigrateContractResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovTx(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgStoreCode) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgStoreCode: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgStoreCode: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field WasmByteCode", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.WasmByteCode = append(m.WasmByteCode[:0], dAtA[iNdEx:postIndex]...) + if m.WasmByteCode == nil { + m.WasmByteCode = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgStoreCodeResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgStoreCodeResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgStoreCodeResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Checksum", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Checksum = append(m.Checksum[:0], dAtA[iNdEx:postIndex]...) + if m.Checksum == nil { + m.Checksum = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgRemoveChecksum) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgRemoveChecksum: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgRemoveChecksum: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Checksum", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Checksum = append(m.Checksum[:0], dAtA[iNdEx:postIndex]...) + if m.Checksum == nil { + m.Checksum = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgRemoveChecksumResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgRemoveChecksumResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgRemoveChecksumResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgMigrateContract) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgMigrateContract: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgMigrateContract: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Checksum", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Checksum = append(m.Checksum[:0], dAtA[iNdEx:postIndex]...) + if m.Checksum == nil { + m.Checksum = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Msg", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Msg = append(m.Msg[:0], dAtA[iNdEx:postIndex]...) + if m.Msg == nil { + m.Msg = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgMigrateContractResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgMigrateContractResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgMigrateContractResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTx(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTx + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTx + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTx + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTx = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/light-clients/08-wasm/types/types_test.go b/modules/light-clients/08-wasm/types/types_test.go new file mode 100644 index 00000000000..49d2e2595a0 --- /dev/null +++ b/modules/light-clients/08-wasm/types/types_test.go @@ -0,0 +1,124 @@ +package types_test + +import ( + "encoding/json" + "errors" + "testing" + + wasmvm "github.com/CosmWasm/wasmvm" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + dbm "github.com/cosmos/cosmos-db" + testifysuite "github.com/stretchr/testify/suite" + + "cosmossdk.io/log" + storetypes "cosmossdk.io/store/types" + + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" + simapp "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing/simapp" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + "github.com/cosmos/ibc-go/v8/modules/core/exported" + ibctesting "github.com/cosmos/ibc-go/v8/testing" +) + +const ( + tmClientID = "07-tendermint-0" + defaultWasmClientID = "08-wasm-0" +) + +type TypesTestSuite struct { + testifysuite.Suite + coordinator *ibctesting.Coordinator + chainA *ibctesting.TestChain + mockVM *wasmtesting.MockWasmEngine + + checksum []byte +} + +func TestWasmTestSuite(t *testing.T) { + testifysuite.Run(t, new(TypesTestSuite)) +} + +func (suite *TypesTestSuite) SetupTest() { + ibctesting.DefaultTestingAppInit = setupTestingApp + + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 1) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) +} + +func init() { + ibctesting.DefaultTestingAppInit = setupTestingApp +} + +// GetSimApp returns the duplicated SimApp from within the 08-wasm directory. +// This must be used instead of chain.GetSimApp() for tests within this directory. +func GetSimApp(chain *ibctesting.TestChain) *simapp.SimApp { + app, ok := chain.App.(*simapp.SimApp) + if !ok { + panic(errors.New("chain is not a simapp.SimApp")) + } + return app +} + +// setupTestingApp provides the duplicated simapp which is specific to the 08-wasm module on chain creation. +func setupTestingApp() (ibctesting.TestingApp, map[string]json.RawMessage) { + db := dbm.NewMemDB() + app := simapp.NewSimApp(log.NewNopLogger(), db, nil, true, simtestutil.EmptyAppOptions{}, nil) + return app, app.DefaultGenesis() +} + +// SetupWasmWithMockVM sets up mock cometbft chain with a mock vm. +func (suite *TypesTestSuite) SetupWasmWithMockVM() { + ibctesting.DefaultTestingAppInit = suite.setupWasmWithMockVM + + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 1) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.checksum = storeWasmCode(suite, wasmtesting.Code) +} + +func (suite *TypesTestSuite) setupWasmWithMockVM() (ibctesting.TestingApp, map[string]json.RawMessage) { + suite.mockVM = wasmtesting.NewMockWasmEngine() + + suite.mockVM.InstantiateFn = func(checksum wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + var payload types.InstantiateMessage + err := json.Unmarshal(initMsg, &payload) + suite.Require().NoError(err) + + store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), payload.ClientState)) + store.Set(host.ConsensusStateKey(payload.ClientState.LatestHeight), clienttypes.MustMarshalConsensusState(suite.chainA.App.AppCodec(), payload.ConsensusState)) + + resp, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.Response{Data: resp}, 0, nil + } + + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(checksum wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) ([]byte, uint64, error) { + resp, err := json.Marshal(types.StatusResult{Status: exported.Active.String()}) + suite.Require().NoError(err) + return resp, wasmtesting.DefaultGasUsed, nil + }) + + db := dbm.NewMemDB() + app := simapp.NewSimApp(log.NewNopLogger(), db, nil, true, simtestutil.EmptyAppOptions{}, suite.mockVM) + + // reset DefaultTestingAppInit to its original value + ibctesting.DefaultTestingAppInit = setupTestingApp + return app, app.DefaultGenesis() +} + +// storeWasmCode stores the wasm code on chain and returns the checksum. +func storeWasmCode(suite *TypesTestSuite, wasmCode []byte) []byte { + ctx := suite.chainA.GetContext().WithBlockGasMeter(storetypes.NewInfiniteGasMeter()) + + msg := types.NewMsgStoreCode(authtypes.NewModuleAddress(govtypes.ModuleName).String(), wasmCode) + response, err := GetSimApp(suite.chainA).WasmClientKeeper.StoreCode(ctx, msg) + suite.Require().NoError(err) + suite.Require().NotNil(response.Checksum) + return response.Checksum +} diff --git a/modules/light-clients/08-wasm/types/update.go b/modules/light-clients/08-wasm/types/update.go new file mode 100644 index 00000000000..97f58c46696 --- /dev/null +++ b/modules/light-clients/08-wasm/types/update.go @@ -0,0 +1,75 @@ +package types + +import ( + "fmt" + + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + ibcerrors "github.com/cosmos/ibc-go/v8/modules/core/errors" + "github.com/cosmos/ibc-go/v8/modules/core/exported" +) + +var _ exported.ClientState = (*ClientState)(nil) + +// VerifyClientMessage must verify a ClientMessage. A ClientMessage could be a Header, Misbehaviour, or batch update. +// It must handle each type of ClientMessage appropriately. Calls to CheckForMisbehaviour, UpdateState, and UpdateStateOnMisbehaviour +// will assume that the content of the ClientMessage has been verified and can be trusted. An error should be returned +// if the ClientMessage fails to verify. +func (cs ClientState) VerifyClientMessage(ctx sdk.Context, _ codec.BinaryCodec, clientStore storetypes.KVStore, clientMsg exported.ClientMessage) error { + clientMessage, ok := clientMsg.(*ClientMessage) + if !ok { + return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected type: %T, got: %T", &ClientMessage{}, clientMsg) + } + + payload := QueryMsg{ + VerifyClientMessage: &VerifyClientMessageMsg{ClientMessage: clientMessage}, + } + _, err := wasmQuery[EmptyResult](ctx, clientStore, &cs, payload) + return err +} + +// Client state and new consensus states are updated in the store by the contract +func (cs ClientState) UpdateState(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, clientMsg exported.ClientMessage) []exported.Height { + clientMessage, ok := clientMsg.(*ClientMessage) + if !ok { + panic(fmt.Errorf("expected type %T, got %T", &ClientMessage{}, clientMsg)) + } + + payload := SudoMsg{ + UpdateState: &UpdateStateMsg{ClientMessage: clientMessage}, + } + + result, err := wasmSudo[UpdateStateResult](ctx, cdc, payload, clientStore, &cs) + if err != nil { + panic(err) + } + + heights := []exported.Height{} + for _, height := range result.Heights { + heights = append(heights, height) + } + + return heights +} + +// UpdateStateOnMisbehaviour should perform appropriate state changes on a client state given that misbehaviour has been detected and verified +// Client state is updated in the store by contract. +func (cs ClientState) UpdateStateOnMisbehaviour(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, clientMsg exported.ClientMessage) { + clientMessage, ok := clientMsg.(*ClientMessage) + if !ok { + panic(fmt.Errorf("expected type %T, got %T", &ClientMessage{}, clientMsg)) + } + + payload := SudoMsg{ + UpdateStateOnMisbehaviour: &UpdateStateOnMisbehaviourMsg{ClientMessage: clientMessage}, + } + + _, err := wasmSudo[EmptyResult](ctx, cdc, payload, clientStore, &cs) + if err != nil { + panic(err) + } +} diff --git a/modules/light-clients/08-wasm/types/update_test.go b/modules/light-clients/08-wasm/types/update_test.go new file mode 100644 index 00000000000..27a82a2612c --- /dev/null +++ b/modules/light-clients/08-wasm/types/update_test.go @@ -0,0 +1,285 @@ +package types_test + +import ( + "encoding/json" + "fmt" + + wasmvm "github.com/CosmWasm/wasmvm" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + "github.com/cosmos/ibc-go/v8/modules/core/exported" + tmtypes "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v8/testing" +) + +func (suite *TypesTestSuite) TestUpdateState() { + mockHeight := clienttypes.NewHeight(1, 1) + + var ( + clientMsg exported.ClientMessage + clientStore storetypes.KVStore + expectedClientStateBz []byte + ) + + testCases := []struct { + name string + malleate func() + expPanic error + expHeights []exported.Height + }{ + { + "success: no update", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, env wasmvmtypes.Env, sudoMsg []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + var msg types.SudoMsg + err := json.Unmarshal(sudoMsg, &msg) + suite.Require().NoError(err) + + suite.Require().NotNil(msg.UpdateState) + suite.Require().NotNil(msg.UpdateState.ClientMessage) + suite.Require().Equal(msg.UpdateState.ClientMessage.Data, wasmtesting.CreateMockClientStateBz(suite.chainA.Codec, suite.checksum)) + suite.Require().Nil(msg.VerifyMembership) + suite.Require().Nil(msg.VerifyNonMembership) + suite.Require().Nil(msg.UpdateStateOnMisbehaviour) + suite.Require().Nil(msg.VerifyUpgradeAndUpdateState) + + suite.Require().Equal(env.Contract.Address, defaultWasmClientID) + + updateStateResp := types.UpdateStateResult{ + Heights: []clienttypes.Height{}, + } + + resp, err := json.Marshal(updateStateResp) + if err != nil { + return nil, 0, err + } + + return &wasmvmtypes.Response{ + Data: resp, + }, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + []exported.Height{}, + }, + { + "success: update client", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + var msg types.SudoMsg + err := json.Unmarshal(sudoMsg, &msg) + suite.Require().NoError(err) + + bz := store.Get(host.ClientStateKey()) + suite.Require().NotEmpty(bz) + clientState := clienttypes.MustUnmarshalClientState(suite.chainA.Codec, bz).(*types.ClientState) + clientState.Data = msg.UpdateState.ClientMessage.Data + expectedClientStateBz = clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), clientState) + store.Set(host.ClientStateKey(), expectedClientStateBz) + + updateStateResp := types.UpdateStateResult{ + Heights: []clienttypes.Height{mockHeight}, + } + + resp, err := json.Marshal(updateStateResp) + if err != nil { + return nil, 0, err + } + + return &wasmvmtypes.Response{ + Data: resp, + }, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + []exported.Height{mockHeight}, + }, + { + "failure: clientStore prefix does not include clientID", + func() { + clientStore = suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), ibctesting.InvalidID) + }, + errorsmod.Wrap(types.ErrWasmContractCallFailed, errorsmod.Wrap(errorsmod.Wrapf(types.ErrRetrieveClientID, "prefix does not contain a valid clientID: %s", errorsmod.Wrapf(host.ErrInvalidID, "invalid client identifier %s", ibctesting.InvalidID)), "failed to retrieve clientID for wasm contract call").Error()), + nil, + }, + { + "failure: invalid ClientMessage type", + func() { + // SudoCallback left nil because clientMsg is checked by 08-wasm before callbackFn is called. + clientMsg = &tmtypes.Misbehaviour{} + }, + fmt.Errorf("expected type %T, got %T", (*types.ClientMessage)(nil), (*tmtypes.Misbehaviour)(nil)), + nil, + }, + { + "failure: callbackFn returns error", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + return nil, 0, wasmtesting.ErrMockContract + }) + }, + errorsmod.Wrap(types.ErrWasmContractCallFailed, wasmtesting.ErrMockContract.Error()), + nil, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() // reset + expectedClientStateBz = nil + + clientMsg = &types.ClientMessage{ + Data: wasmtesting.CreateMockClientStateBz(suite.chainA.Codec, suite.checksum), + } + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + clientStore = suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) + + tc.malleate() + + clientState := endpoint.GetClientState() + + var heights []exported.Height + updateState := func() { + heights = clientState.UpdateState(suite.chainA.GetContext(), suite.chainA.Codec, clientStore, clientMsg) + } + + if tc.expPanic == nil { + updateState() + suite.Require().Equal(tc.expHeights, heights) + + if expectedClientStateBz != nil { + clientStateBz := clientStore.Get(host.ClientStateKey()) + suite.Require().Equal(expectedClientStateBz, clientStateBz) + } + } else { + suite.Require().PanicsWithError(tc.expPanic.Error(), updateState) + } + }) + } +} + +func (suite *TypesTestSuite) TestUpdateStateOnMisbehaviour() { + var clientMsg exported.ClientMessage + + var expectedClientStateBz []byte + + testCases := []struct { + name string + malleate func() + panicErr error + updatedClientState []byte + }{ + { + "success: no update", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateOnMisbehaviourMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + var msg types.SudoMsg + + err := json.Unmarshal(sudoMsg, &msg) + suite.Require().NoError(err) + + suite.Require().NotNil(msg.UpdateStateOnMisbehaviour) + suite.Require().NotNil(msg.UpdateStateOnMisbehaviour.ClientMessage) + suite.Require().Nil(msg.UpdateState) + suite.Require().Nil(msg.UpdateState) + suite.Require().Nil(msg.VerifyMembership) + suite.Require().Nil(msg.VerifyNonMembership) + suite.Require().Nil(msg.VerifyUpgradeAndUpdateState) + + resp, err := json.Marshal(types.EmptyResult{}) + if err != nil { + return nil, 0, err + } + + return &wasmvmtypes.Response{ + Data: resp, + }, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + nil, + }, + { + "success: client state updated on valid misbehaviour", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateOnMisbehaviourMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + var msg types.SudoMsg + err := json.Unmarshal(sudoMsg, &msg) + suite.Require().NoError(err) + + // set new client state in store + expectedClientStateBz = msg.UpdateStateOnMisbehaviour.ClientMessage.Data + store.Set(host.ClientStateKey(), expectedClientStateBz) + resp, err := json.Marshal(types.EmptyResult{}) + if err != nil { + return nil, 0, err + } + + return &wasmvmtypes.Response{Data: resp}, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + wasmtesting.MockClientStateBz, + }, + { + "failure: invalid client message", + func() { + clientMsg = &tmtypes.Header{} + // we will not register the callback here because this test case does not reach the VM + }, + fmt.Errorf("expected type %T, got %T", (*types.ClientMessage)(nil), (*tmtypes.Header)(nil)), + nil, + }, + { + "failure: err return from contract vm", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateOnMisbehaviourMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + return nil, 0, wasmtesting.ErrMockContract + }) + }, + errorsmod.Wrap(types.ErrWasmContractCallFailed, wasmtesting.ErrMockContract.Error()), + nil, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + // reset suite to create fresh application state + suite.SetupWasmWithMockVM() + expectedClientStateBz = nil + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) + clientMsg = &types.ClientMessage{ + Data: wasmtesting.CreateMockClientStateBz(suite.chainA.Codec, suite.checksum), + } + clientState := endpoint.GetClientState() + + tc.malleate() + + if tc.panicErr == nil { + clientState.UpdateStateOnMisbehaviour(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), store, clientMsg) + if expectedClientStateBz != nil { + suite.Require().Equal(expectedClientStateBz, store.Get(host.ClientStateKey())) + } + } else { + suite.Require().PanicsWithError(tc.panicErr.Error(), func() { + clientState.UpdateStateOnMisbehaviour(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), store, clientMsg) + }) + } + }) + } +} diff --git a/modules/light-clients/08-wasm/types/upgrade.go b/modules/light-clients/08-wasm/types/upgrade.go new file mode 100644 index 00000000000..1fbea2a3099 --- /dev/null +++ b/modules/light-clients/08-wasm/types/upgrade.go @@ -0,0 +1,57 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + ibcerrors "github.com/cosmos/ibc-go/v8/modules/core/errors" + "github.com/cosmos/ibc-go/v8/modules/core/exported" +) + +// VerifyUpgradeAndUpdateState, on a successful verification expects the contract to update +// the new client state, consensus state, and any other client metadata. +func (cs ClientState) VerifyUpgradeAndUpdateState( + ctx sdk.Context, + cdc codec.BinaryCodec, + clientStore storetypes.KVStore, + upgradedClient exported.ClientState, + upgradedConsState exported.ConsensusState, + proofUpgradeClient, + proofUpgradeConsState []byte, +) error { + wasmUpgradeClientState, ok := upgradedClient.(*ClientState) + if !ok { + return errorsmod.Wrapf(clienttypes.ErrInvalidClient, "upgraded client state must be wasm light client state. expected %T, got: %T", + &ClientState{}, wasmUpgradeClientState) + } + + wasmUpgradeConsState, ok := upgradedConsState.(*ConsensusState) + if !ok { + return errorsmod.Wrapf(clienttypes.ErrInvalidConsensus, "upgraded consensus state must be wasm light consensus state. expected %T, got: %T", + &ConsensusState{}, wasmUpgradeConsState) + } + + // last height of current counterparty chain must be client's latest height + lastHeight := cs.GetLatestHeight() + + if !upgradedClient.GetLatestHeight().GT(lastHeight) { + return errorsmod.Wrapf(ibcerrors.ErrInvalidHeight, "upgraded client height %s must be greater than current client height %s", + upgradedClient.GetLatestHeight(), lastHeight) + } + + payload := SudoMsg{ + VerifyUpgradeAndUpdateState: &VerifyUpgradeAndUpdateStateMsg{ + UpgradeClientState: *wasmUpgradeClientState, + UpgradeConsensusState: *wasmUpgradeConsState, + ProofUpgradeClient: proofUpgradeClient, + ProofUpgradeConsensusState: proofUpgradeConsState, + }, + } + + _, err := wasmSudo[EmptyResult](ctx, cdc, payload, clientStore, &cs) + return err +} diff --git a/modules/light-clients/08-wasm/types/upgrade_test.go b/modules/light-clients/08-wasm/types/upgrade_test.go new file mode 100644 index 00000000000..b77efcfe5cb --- /dev/null +++ b/modules/light-clients/08-wasm/types/upgrade_test.go @@ -0,0 +1,243 @@ +package types_test + +import ( + "encoding/json" + + wasmvm "github.com/CosmWasm/wasmvm" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + + storetypes "cosmossdk.io/store/types" + + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v8/modules/core/errors" + "github.com/cosmos/ibc-go/v8/modules/core/exported" + solomachine "github.com/cosmos/ibc-go/v8/modules/light-clients/06-solomachine" + ibctmtypes "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v8/testing" +) + +func (suite *TypesTestSuite) TestVerifyClientMessage() { + var ( + clientMsg exported.ClientMessage + clientStore storetypes.KVStore + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success: valid misbehaviour", + func() { + suite.mockVM.RegisterQueryCallback(types.VerifyClientMessageMsg{}, func(_ wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) ([]byte, uint64, error) { + var msg *types.QueryMsg + + err := json.Unmarshal(queryMsg, &msg) + suite.Require().NoError(err) + + suite.Require().NotNil(msg.VerifyClientMessage) + suite.Require().NotNil(msg.VerifyClientMessage.ClientMessage) + suite.Require().Nil(msg.Status) + suite.Require().Nil(msg.CheckForMisbehaviour) + suite.Require().Nil(msg.TimestampAtHeight) + suite.Require().Nil(msg.ExportMetadata) + + suite.Require().Equal(env.Contract.Address, defaultWasmClientID) + + resp, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return resp, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + }, + { + "failure: clientStore prefix does not include clientID", + func() { + clientStore = suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), ibctesting.InvalidID) + }, + types.ErrWasmContractCallFailed, + }, + { + "failure: invalid client message", + func() { + clientMsg = &ibctmtypes.Header{} + + suite.mockVM.RegisterQueryCallback(types.VerifyClientMessageMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, queryMsg []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) ([]byte, uint64, error) { + resp, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return resp, wasmtesting.DefaultGasUsed, nil + }) + }, + ibcerrors.ErrInvalidType, + }, + { + "failure: error return from contract vm", + func() { + suite.mockVM.RegisterQueryCallback(types.VerifyClientMessageMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, queryMsg []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) ([]byte, uint64, error) { + return nil, 0, wasmtesting.ErrMockContract + }) + }, + types.ErrWasmContractCallFailed, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + // reset suite to create fresh application state + suite.SetupWasmWithMockVM() + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + + clientStore = suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) + clientState := endpoint.GetClientState() + + clientMsg = &types.ClientMessage{ + Data: []byte{1}, + } + + tc.malleate() + + err = clientState.VerifyClientMessage(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), clientStore, clientMsg) + + expPass := tc.expErr == nil + if expPass { + suite.Require().NoError(err) + } else { + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *TypesTestSuite) TestVerifyUpgradeAndUpdateState() { + var ( + upgradedClient exported.ClientState + upgradedConsState exported.ConsensusState + proofUpgradedClient []byte + proofUpgradedConsState []byte + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success: successful upgrade", + func() { + suite.mockVM.RegisterSudoCallback(types.VerifyUpgradeAndUpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + var payload types.SudoMsg + + err := json.Unmarshal(sudoMsg, &payload) + suite.Require().NoError(err) + + // verify payload values + suite.Require().Equal(upgradedClient, &payload.VerifyUpgradeAndUpdateState.UpgradeClientState) + suite.Require().Equal(upgradedConsState, &payload.VerifyUpgradeAndUpdateState.UpgradeConsensusState) + suite.Require().Equal(proofUpgradedClient, payload.VerifyUpgradeAndUpdateState.ProofUpgradeClient) + suite.Require().Equal(proofUpgradedConsState, payload.VerifyUpgradeAndUpdateState.ProofUpgradeConsensusState) + + // verify other Sudo fields are nil + suite.Require().Nil(payload.UpdateState) + suite.Require().Nil(payload.UpdateStateOnMisbehaviour) + suite.Require().Nil(payload.VerifyMembership) + suite.Require().Nil(payload.VerifyNonMembership) + + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + // set new client state and consensus state + store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), upgradedClient)) + store.Set(host.ConsensusStateKey(upgradedClient.GetLatestHeight()), clienttypes.MustMarshalConsensusState(suite.chainA.App.AppCodec(), upgradedConsState)) + + return &wasmvmtypes.Response{Data: data}, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + }, + { + "failure: upgraded client state is not wasm client state", + func() { + // set upgraded client state to solomachine client state + upgradedClient = &solomachine.ClientState{} + }, + clienttypes.ErrInvalidClient, + }, + { + "failure: upgraded consensus state is not wasm consensus state", + func() { + // set upgraded consensus state to solomachine consensus state + upgradedConsState = &solomachine.ConsensusState{} + }, + clienttypes.ErrInvalidConsensus, + }, + { + "failure: contract returns error", + func() { + suite.mockVM.RegisterSudoCallback(types.VerifyUpgradeAndUpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + return nil, 0, wasmtesting.ErrMockContract + }) + }, + types.ErrWasmContractCallFailed, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + // reset suite + suite.SetupWasmWithMockVM() + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + + clientState := endpoint.GetClientState().(*types.ClientState) + + upgradedClient = types.NewClientState(wasmtesting.MockClientStateBz, clientState.Checksum, clienttypes.NewHeight(0, clientState.GetLatestHeight().GetRevisionHeight()+1)) + upgradedConsState = &types.ConsensusState{wasmtesting.MockConsensusStateBz} + + tc.malleate() + + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), defaultWasmClientID) + + proofUpgradedClient = wasmtesting.MockUpgradedClientStateProofBz + proofUpgradedConsState = wasmtesting.MockUpgradedConsensusStateProofBz + + err = clientState.VerifyUpgradeAndUpdateState( + suite.chainA.GetContext(), + suite.chainA.Codec, + clientStore, + upgradedClient, + upgradedConsState, + proofUpgradedClient, + proofUpgradedConsState, + ) + + expPass := tc.expErr == nil + if expPass { + suite.Require().NoError(err) + + // verify new client state and consensus state + clientStateBz := clientStore.Get(host.ClientStateKey()) + suite.Require().NotEmpty(clientStateBz) + suite.Require().Equal(upgradedClient, clienttypes.MustUnmarshalClientState(suite.chainA.Codec, clientStateBz)) + + consStateBz := clientStore.Get(host.ConsensusStateKey(upgradedClient.GetLatestHeight())) + suite.Require().NotEmpty(consStateBz) + suite.Require().Equal(upgradedConsState, clienttypes.MustUnmarshalConsensusState(suite.chainA.Codec, consStateBz)) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} diff --git a/modules/light-clients/08-wasm/types/utils.go b/modules/light-clients/08-wasm/types/utils.go new file mode 100644 index 00000000000..88d333490e7 --- /dev/null +++ b/modules/light-clients/08-wasm/types/utils.go @@ -0,0 +1,71 @@ +package types + +import ( + "bytes" + "compress/gzip" + "io" +) + +// Copied gzip feature from wasmd +// https://github.com/CosmWasm/wasmd/blob/v0.31.0/x/wasm/ioutils/utils.go + +// Note: []byte can never be const as they are inherently mutable + +// magic bytes to identify gzip. +// See https://www.ietf.org/rfc/rfc1952.txt +// and https://github.com/golang/go/blob/master/src/net/http/sniff.go#L186 +var gzipIdent = []byte("\x1F\x8B\x08") + +// IsGzip returns checks if the file contents are gzip compressed +func IsGzip(input []byte) bool { + return len(input) >= 3 && bytes.Equal(gzipIdent, input[0:3]) +} + +// Uncompress expects a valid gzip source to unpack or fails. See IsGzip +func Uncompress(gzipSrc []byte, limit uint64) ([]byte, error) { + if uint64(len(gzipSrc)) > limit { + return nil, ErrWasmCodeTooLarge + } + zr, err := gzip.NewReader(bytes.NewReader(gzipSrc)) + if err != nil { + return nil, err + } + zr.Multistream(false) + defer zr.Close() + return io.ReadAll(limitReader(zr, int64(limit))) +} + +// limitReader returns a Reader that reads from r +// but stops with types.ErrLimit after n bytes. +// The underlying implementation is a *io.LimitedReader. +func limitReader(r io.Reader, n int64) io.Reader { + return &limitedReader{r: &io.LimitedReader{R: r, N: n}} +} + +type limitedReader struct { + r *io.LimitedReader +} + +func (l *limitedReader) Read(p []byte) (n int, err error) { + if l.r.N <= 0 { + return 0, ErrWasmCodeTooLarge + } + return l.r.Read(p) +} + +// GzipIt compresses the input ([]byte) +func GzipIt(input []byte) ([]byte, error) { + // Create gzip writer. + var b bytes.Buffer + w := gzip.NewWriter(&b) + _, err := w.Write(input) + if err != nil { + return nil, err + } + err = w.Close() // You must close this first to flush the bytes to the buffer. + if err != nil { + return nil, err + } + + return b.Bytes(), nil +} diff --git a/modules/light-clients/08-wasm/types/validation.go b/modules/light-clients/08-wasm/types/validation.go new file mode 100644 index 00000000000..c5b15474b33 --- /dev/null +++ b/modules/light-clients/08-wasm/types/validation.go @@ -0,0 +1,58 @@ +package types + +import ( + "strings" + + errorsmod "cosmossdk.io/errors" + + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + "github.com/cosmos/ibc-go/v8/modules/core/exported" +) + +const maxWasmSize = 3 * 1024 * 1024 + +// ValidateWasmCode valides that the size of the wasm code is in the allowed range +// and that the contents are of a wasm binary. +func ValidateWasmCode(code []byte) error { + if len(code) == 0 { + return ErrWasmEmptyCode + } + if len(code) > maxWasmSize { + return ErrWasmCodeTooLarge + } + + return nil +} + +// MaxWasmByteSize returns the maximum allowed number of bytes for wasm bytecode +func MaxWasmByteSize() uint64 { + return maxWasmSize +} + +// ValidateWasmChecksum validates that the checksum is of the correct length +func ValidateWasmChecksum(checksum []byte) error { + lenChecksum := len(checksum) + if lenChecksum == 0 { + return errorsmod.Wrap(ErrInvalidChecksum, "checksum cannot be empty") + } + if lenChecksum != 32 { // sha256 output is 256 bits long + return errorsmod.Wrapf(ErrInvalidChecksum, "expected length of 32 bytes, got %d", lenChecksum) + } + + return nil +} + +// ValidateClientID validates the client identifier by ensuring that it conforms +// to the 02-client identifier format and that it is a 08-wasm clientID. +func ValidateClientID(clientID string) error { + if !clienttypes.IsValidClientID(clientID) { + return errorsmod.Wrapf(host.ErrInvalidID, "invalid client identifier %s", clientID) + } + + if !strings.HasPrefix(clientID, exported.Wasm) { + return errorsmod.Wrapf(host.ErrInvalidID, "client identifier %s does not contain %s prefix", clientID, exported.Wasm) + } + + return nil +} diff --git a/modules/light-clients/08-wasm/types/validation_test.go b/modules/light-clients/08-wasm/types/validation_test.go new file mode 100644 index 00000000000..ff9c44bf27b --- /dev/null +++ b/modules/light-clients/08-wasm/types/validation_test.go @@ -0,0 +1,162 @@ +package types_test + +import ( + "crypto/rand" + "crypto/sha256" + "os" + "testing" + + "github.com/stretchr/testify/require" + + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + "github.com/cosmos/ibc-go/v8/modules/core/exported" + ibctesting "github.com/cosmos/ibc-go/v8/testing" +) + +func TestValidateWasmCode(t *testing.T) { + var code []byte + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + code, _ = os.ReadFile("../test_data/ics10_grandpa_cw.wasm.gz") + }, + nil, + }, + { + "failure: empty byte slice", + func() { + code = []byte{} + }, + types.ErrWasmEmptyCode, + }, + { + "failure: byte slice too large", + func() { + expLength := types.MaxWasmByteSize() + 1 + code = make([]byte, expLength) + length, err := rand.Read(code) + require.NoError(t, err, t.Name()) + require.Equal(t, expLength, uint64(length), t.Name()) + }, + types.ErrWasmCodeTooLarge, + }, + } + + for _, tc := range testCases { + tc.malleate() + + err := types.ValidateWasmCode(code) + + if tc.expError == nil { + require.NoError(t, err, tc.name) + } else { + require.ErrorIs(t, err, tc.expError, tc.name) + } + } +} + +func TestValidateWasmChecksum(t *testing.T) { + var checksum []byte + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + code, _ := os.ReadFile("../test_data/ics10_grandpa_cw.wasm.gz") + hash := sha256.Sum256(code) + checksum = hash[:] + }, + nil, + }, + { + "failure: nil byte slice", + func() { + checksum = nil + }, + errorsmod.Wrap(types.ErrInvalidChecksum, "checksum cannot be empty"), + }, + { + "failure: empty byte slice", + func() { + checksum = []byte{} + }, + errorsmod.Wrap(types.ErrInvalidChecksum, "checksum cannot be empty"), + }, + { + "failure: byte slice size is not 32", + func() { + checksum = []byte{1} + }, + errorsmod.Wrapf(types.ErrInvalidChecksum, "expected length of 32 bytes, got %d", 1), + }, + } + + for _, tc := range testCases { + tc.malleate() + + err := types.ValidateWasmChecksum(checksum) + + if tc.expError == nil { + require.NoError(t, err, tc.name) + } else { + require.ErrorContains(t, err, tc.expError.Error(), tc.name) + } + } +} + +func TestValidateClientID(t *testing.T) { + var clientID string + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success: valid wasm client identifier", + func() { + clientID = defaultWasmClientID + }, + nil, + }, + { + "failure: empty clientID", + func() { + clientID = "" + }, + errorsmod.Wrapf(host.ErrInvalidID, "invalid client identifier %s", clientID), + }, + { + "failure: clientID is not a wasm client identifier", + func() { + clientID = ibctesting.FirstClientID + }, + errorsmod.Wrapf(host.ErrInvalidID, "client identifier %s does not contain %s prefix", ibctesting.FirstClientID, exported.Wasm), + }, + } + + for _, tc := range testCases { + tc.malleate() + + err := types.ValidateClientID(clientID) + + if tc.expError == nil { + require.NoError(t, err, tc.name) + } else { + require.ErrorContains(t, err, tc.expError.Error(), tc.name) + } + } +} diff --git a/modules/light-clients/08-wasm/types/vm.go b/modules/light-clients/08-wasm/types/vm.go new file mode 100644 index 00000000000..ac90894b78a --- /dev/null +++ b/modules/light-clients/08-wasm/types/vm.go @@ -0,0 +1,313 @@ +package types + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "errors" + + wasmvm "github.com/CosmWasm/wasmvm" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" + "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + "github.com/cosmos/ibc-go/v8/modules/core/exported" +) + +var ( + VMGasRegister = NewDefaultWasmGasRegister() + // wasmvmAPI is a wasmvm.GoAPI implementation that is passed to the wasmvm, it + // doesn't implement any functionality, directly returning an error. + wasmvmAPI = wasmvm.GoAPI{ + HumanAddress: humanAddress, + CanonicalAddress: canonicalAddress, + } +) + +// instantiateContract calls vm.Instantiate with appropriate arguments. +func instantiateContract(ctx sdk.Context, clientStore storetypes.KVStore, checksum []byte, msg []byte) (*wasmvmtypes.Response, error) { + sdkGasMeter := ctx.GasMeter() + multipliedGasMeter := NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) + gasLimit := VMGasRegister.runtimeGasForContract(ctx) + + clientID, err := getClientID(clientStore) + if err != nil { + return nil, errorsmod.Wrap(err, "failed to retrieve clientID for wasm contract instantiation") + } + env := getEnv(ctx, clientID) + + msgInfo := wasmvmtypes.MessageInfo{ + Sender: "", + Funds: nil, + } + + ctx.GasMeter().ConsumeGas(VMGasRegister.NewContractInstanceCosts(true, len(msg)), "Loading CosmWasm module: instantiate") + response, gasUsed, err := ibcwasm.GetVM().Instantiate(checksum, env, msgInfo, msg, newStoreAdapter(clientStore), wasmvmAPI, nil, multipliedGasMeter, gasLimit, costJSONDeserialization) + VMGasRegister.consumeRuntimeGas(ctx, gasUsed) + return response, err +} + +// callContract calls vm.Sudo with internally constructed gas meter and environment. +func callContract(ctx sdk.Context, clientStore storetypes.KVStore, checksum []byte, msg []byte) (*wasmvmtypes.Response, error) { + sdkGasMeter := ctx.GasMeter() + multipliedGasMeter := NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) + gasLimit := VMGasRegister.runtimeGasForContract(ctx) + + clientID, err := getClientID(clientStore) + if err != nil { + return nil, errorsmod.Wrap(err, "failed to retrieve clientID for wasm contract call") + } + env := getEnv(ctx, clientID) + + ctx.GasMeter().ConsumeGas(VMGasRegister.InstantiateContractCosts(true, len(msg)), "Loading CosmWasm module: sudo") + resp, gasUsed, err := ibcwasm.GetVM().Sudo(checksum, env, msg, newStoreAdapter(clientStore), wasmvmAPI, nil, multipliedGasMeter, gasLimit, costJSONDeserialization) + VMGasRegister.consumeRuntimeGas(ctx, gasUsed) + return resp, err +} + +// migrateContract calls vm.Migrate with internally constructed gas meter and environment. +func migrateContract(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, checksum []byte, msg []byte) (*wasmvmtypes.Response, error) { + sdkGasMeter := ctx.GasMeter() + multipliedGasMeter := NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) + gasLimit := VMGasRegister.runtimeGasForContract(ctx) + + env := getEnv(ctx, clientID) + + ctx.GasMeter().ConsumeGas(VMGasRegister.InstantiateContractCosts(true, len(msg)), "Loading CosmWasm module: migrate") + resp, gasUsed, err := ibcwasm.GetVM().Migrate(checksum, env, msg, newStoreAdapter(clientStore), wasmvmAPI, nil, multipliedGasMeter, gasLimit, costJSONDeserialization) + VMGasRegister.consumeRuntimeGas(ctx, gasUsed) + return resp, err +} + +// queryContract calls vm.Query. +func queryContract(ctx sdk.Context, clientStore storetypes.KVStore, checksum []byte, msg []byte) ([]byte, error) { + sdkGasMeter := ctx.GasMeter() + multipliedGasMeter := NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) + gasLimit := VMGasRegister.runtimeGasForContract(ctx) + + clientID, err := getClientID(clientStore) + if err != nil { + return nil, errorsmod.Wrap(err, "failed to retrieve clientID for wasm contract query") + } + env := getEnv(ctx, clientID) + + ctx.GasMeter().ConsumeGas(VMGasRegister.InstantiateContractCosts(true, len(msg)), "Loading CosmWasm module: query") + resp, gasUsed, err := ibcwasm.GetVM().Query(checksum, env, msg, newStoreAdapter(clientStore), wasmvmAPI, nil, multipliedGasMeter, gasLimit, costJSONDeserialization) + VMGasRegister.consumeRuntimeGas(ctx, gasUsed) + return resp, err +} + +// wasmInstantiate accepts a message to instantiate a wasm contract, JSON encodes it and calls instantiateContract. +func wasmInstantiate(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *ClientState, payload InstantiateMessage) error { + encodedData, err := json.Marshal(payload) + if err != nil { + return errorsmod.Wrap(err, "failed to marshal payload for wasm contract instantiation") + } + + checksum := cs.Checksum + resp, err := instantiateContract(ctx, clientStore, checksum, encodedData) + if err != nil { + return errorsmod.Wrap(ErrWasmContractCallFailed, err.Error()) + } + + if err = checkResponse(resp); err != nil { + return errorsmod.Wrapf(err, "checksum (%s)", hex.EncodeToString(cs.Checksum)) + } + + newClientState, err := validatePostExecutionClientState(clientStore, cdc) + if err != nil { + return err + } + + // Checksum should only be able to be modified during migration. + if !bytes.Equal(checksum, newClientState.Checksum) { + return errorsmod.Wrapf(ErrWasmInvalidContractModification, "expected checksum %s, got %s", hex.EncodeToString(checksum), hex.EncodeToString(newClientState.Checksum)) + } + + return nil +} + +// wasmSudo calls the contract with the given payload and returns the result. +// wasmSudo returns an error if: +// - the payload cannot be marshaled to JSON +// - the contract call returns an error +// - the response of the contract call contains non-empty messages +// - the response of the contract call contains non-empty events +// - the response of the contract call contains non-empty attributes +// - the data bytes of the response cannot be unmarshaled into the result type +func wasmSudo[T ContractResult](ctx sdk.Context, cdc codec.BinaryCodec, payload SudoMsg, clientStore storetypes.KVStore, cs *ClientState) (T, error) { + var result T + + encodedData, err := json.Marshal(payload) + if err != nil { + return result, errorsmod.Wrap(err, "failed to marshal payload for wasm execution") + } + + checksum := cs.Checksum + resp, err := callContract(ctx, clientStore, checksum, encodedData) + if err != nil { + return result, errorsmod.Wrap(ErrWasmContractCallFailed, err.Error()) + } + + if err = checkResponse(resp); err != nil { + return result, errorsmod.Wrapf(err, "checksum (%s)", hex.EncodeToString(cs.Checksum)) + } + + if err := json.Unmarshal(resp.Data, &result); err != nil { + return result, errorsmod.Wrap(ErrWasmInvalidResponseData, err.Error()) + } + + newClientState, err := validatePostExecutionClientState(clientStore, cdc) + if err != nil { + return result, err + } + + // Checksum should only be able to be modified during migration. + if !bytes.Equal(checksum, newClientState.Checksum) { + return result, errorsmod.Wrapf(ErrWasmInvalidContractModification, "expected checksum %s, got %s", hex.EncodeToString(checksum), hex.EncodeToString(newClientState.Checksum)) + } + + return result, nil +} + +// validatePostExecutionClientState validates that the contract has not many any invalid modifications +// to the client state during execution. It ensures that +// - the client state is still present +// - the client state can be unmarshaled successfully. +// - the client state is of type *ClientState +func validatePostExecutionClientState(clientStore storetypes.KVStore, cdc codec.BinaryCodec) (*ClientState, error) { + key := host.ClientStateKey() + _, ok := clientStore.(migrateClientWrappedStore) + if ok { + key = append(subjectPrefix, key...) + } + + bz := clientStore.Get(key) + if len(bz) == 0 { + return nil, errorsmod.Wrap(ErrWasmInvalidContractModification, types.ErrClientNotFound.Error()) + } + + clientState, err := unmarshalClientState(cdc, bz) + if err != nil { + return nil, errorsmod.Wrap(ErrWasmInvalidContractModification, err.Error()) + } + + cs, ok := clientState.(*ClientState) + if !ok { + return nil, errorsmod.Wrapf(ErrWasmInvalidContractModification, "expected client state type %T, got %T", (*ClientState)(nil), clientState) + } + + return cs, nil +} + +// unmarshalClientState unmarshals the client state from the given bytes. +func unmarshalClientState(cdc codec.BinaryCodec, bz []byte) (exported.ClientState, error) { + var clientState exported.ClientState + if err := cdc.UnmarshalInterface(bz, &clientState); err != nil { + return nil, err + } + + return clientState, nil +} + +// wasmMigrate migrate calls the migrate entry point of the contract with the given payload and returns the result. +// wasmMigrate returns an error if: +// - the contract migration returns an error +func wasmMigrate(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, cs *ClientState, clientID string, payload []byte) error { + resp, err := migrateContract(ctx, clientID, clientStore, cs.Checksum, payload) + if err != nil { + return errorsmod.Wrapf(ErrWasmContractCallFailed, err.Error()) + } + + if err = checkResponse(resp); err != nil { + return errorsmod.Wrapf(err, "checksum (%s)", hex.EncodeToString(cs.Checksum)) + } + + _, err = validatePostExecutionClientState(clientStore, cdc) + return err +} + +// wasmQuery queries the contract with the given payload and returns the result. +// wasmQuery returns an error if: +// - the payload cannot be marshaled to JSON +// - the contract query returns an error +// - the data bytes of the response cannot be unmarshal into the result type +func wasmQuery[T ContractResult](ctx sdk.Context, clientStore storetypes.KVStore, cs *ClientState, payload QueryMsg) (T, error) { + var result T + + encodedData, err := json.Marshal(payload) + if err != nil { + return result, errorsmod.Wrap(err, "failed to marshal payload for wasm query") + } + + resp, err := queryContract(ctx, clientStore, cs.Checksum, encodedData) + if err != nil { + return result, errorsmod.Wrap(ErrWasmContractCallFailed, err.Error()) + } + + if err := json.Unmarshal(resp, &result); err != nil { + return result, errorsmod.Wrapf(ErrWasmInvalidResponseData, "failed to unmarshal result of wasm query: %v", err) + } + + return result, nil +} + +// getEnv returns the state of the blockchain environment the contract is running on +func getEnv(ctx sdk.Context, contractAddr string) wasmvmtypes.Env { + chainID := ctx.BlockHeader().ChainID + height := ctx.BlockHeader().Height + + // safety checks before casting below + if height < 0 { + panic(errors.New("block height must never be negative")) + } + nsec := ctx.BlockTime().UnixNano() + if nsec < 0 { + panic(errors.New("block (unix) time must never be negative ")) + } + + env := wasmvmtypes.Env{ + Block: wasmvmtypes.BlockInfo{ + Height: uint64(height), + Time: uint64(nsec), + ChainID: chainID, + }, + Contract: wasmvmtypes.ContractInfo{ + Address: contractAddr, + }, + } + + return env +} + +func humanAddress(canon []byte) (string, uint64, error) { + return "", 0, errors.New("humanAddress not implemented") +} + +func canonicalAddress(human string) ([]byte, uint64, error) { + return nil, 0, errors.New("canonicalAddress not implemented") +} + +// checkResponse returns an error if the response from a sudo, instantiate or migrate call +// to the Wasm VM contains messages, events or attributes. +func checkResponse(response *wasmvmtypes.Response) error { + // Only allow Data to flow back to us. SubMessages, Events and Attributes are not allowed. + if len(response.Messages) > 0 { + return ErrWasmSubMessagesNotAllowed + } + if len(response.Events) > 0 { + return ErrWasmEventsNotAllowed + } + if len(response.Attributes) > 0 { + return ErrWasmAttributesNotAllowed + } + + return nil +} diff --git a/modules/light-clients/08-wasm/types/vm_test.go b/modules/light-clients/08-wasm/types/vm_test.go new file mode 100644 index 00000000000..bc1eed6ffdc --- /dev/null +++ b/modules/light-clients/08-wasm/types/vm_test.go @@ -0,0 +1,541 @@ +package types_test + +import ( + "encoding/json" + + wasmvm "github.com/CosmWasm/wasmvm" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + "github.com/cosmos/ibc-go/v8/modules/core/exported" + localhost "github.com/cosmos/ibc-go/v8/modules/light-clients/09-localhost" +) + +func (suite *TypesTestSuite) TestWasmInstantiate() { + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + // Ensure GoAPI is set + suite.Require().NotNil(goapi.CanonicalAddress) + suite.Require().NotNil(goapi.HumanAddress) + + var payload types.InstantiateMessage + err := json.Unmarshal(initMsg, &payload) + suite.Require().NoError(err) + + store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), payload.ClientState)) + store.Set(host.ConsensusStateKey(payload.ClientState.LatestHeight), clienttypes.MustMarshalConsensusState(suite.chainA.App.AppCodec(), payload.ConsensusState)) + + return &wasmvmtypes.Response{}, 0, nil + } + }, + nil, + }, + { + "failure: contract returns error", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + return nil, 0, wasmtesting.ErrMockContract + } + }, + types.ErrWasmContractCallFailed, + }, + { + "failure: contract returns non-empty messages", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + resp := wasmvmtypes.Response{Messages: []wasmvmtypes.SubMsg{{}}} + + return &resp, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmSubMessagesNotAllowed, + }, + { + "failure: contract returns non-empty events", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + resp := wasmvmtypes.Response{Events: []wasmvmtypes.Event{{}}} + + return &resp, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmEventsNotAllowed, + }, + { + "failure: contract returns non-empty attributes", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + resp := wasmvmtypes.Response{Attributes: []wasmvmtypes.EventAttribute{{}}} + + return &resp, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmAttributesNotAllowed, + }, + { + "failure: change clientstate type", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + newClientState := localhost.NewClientState(clienttypes.NewHeight(1, 1)) + store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), newClientState)) + + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + return &wasmvmtypes.Response{Data: data}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmInvalidContractModification, + }, + { + "failure: delete clientstate", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + store.Delete(host.ClientStateKey()) + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + return &wasmvmtypes.Response{Data: data}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmInvalidContractModification, + }, + { + "failure: unmarshallable clientstate", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + store.Set(host.ClientStateKey(), []byte("invalid json")) + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + return &wasmvmtypes.Response{Data: data}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmInvalidContractModification, + }, + { + "failure: change checksum", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + var payload types.InstantiateMessage + err := json.Unmarshal(initMsg, &payload) + suite.Require().NoError(err) + + // Change the checksum to something else. + clientState := payload.ClientState + clientState.Checksum = []byte("new checksum") + store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), clientState)) + + resp, err := json.Marshal(types.UpdateStateResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.Response{Data: resp}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmInvalidContractModification, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + tc.malleate() + + initMsg := types.InstantiateMessage{ + ClientState: &types.ClientState{}, + ConsensusState: &types.ConsensusState{}, + } + + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), defaultWasmClientID) + err := types.WasmInstantiate(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), clientStore, &types.ClientState{}, initMsg) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} + +func (suite *TypesTestSuite) TestWasmMigrate() { + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, goapi wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + // Ensure GoAPI is set + suite.Require().NotNil(goapi.CanonicalAddress) + suite.Require().NotNil(goapi.HumanAddress) + + resp, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.Response{Data: resp}, 0, nil + } + }, + nil, + }, + { + "failure: contract returns error", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + return nil, 0, wasmtesting.ErrMockContract + } + }, + types.ErrWasmContractCallFailed, + }, + { + "failure: contract returns non-empty messages", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + resp := wasmvmtypes.Response{Messages: []wasmvmtypes.SubMsg{{}}} + + return &resp, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmSubMessagesNotAllowed, + }, + { + "failure: contract returns non-empty events", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + resp := wasmvmtypes.Response{Events: []wasmvmtypes.Event{{}}} + + return &resp, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmEventsNotAllowed, + }, + { + "failure: contract returns non-empty attributes", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + resp := wasmvmtypes.Response{Attributes: []wasmvmtypes.EventAttribute{{}}} + + return &resp, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmAttributesNotAllowed, + }, + { + "failure: change clientstate type", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + newClientState := localhost.NewClientState(clienttypes.NewHeight(1, 1)) + store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), newClientState)) + + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + return &wasmvmtypes.Response{Data: data}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmInvalidContractModification, + }, + { + "failure: delete clientstate", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + store.Delete(host.ClientStateKey()) + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + return &wasmvmtypes.Response{Data: data}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmInvalidContractModification, + }, + { + "failure: unmarshallable clientstate", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + store.Set(host.ClientStateKey(), []byte("invalid json")) + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + return &wasmvmtypes.Response{Data: data}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmInvalidContractModification, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + + tc.malleate() + + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), defaultWasmClientID) + err = types.WasmMigrate(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), clientStore, &types.ClientState{}, defaultWasmClientID, []byte("{}")) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} + +func (suite *TypesTestSuite) TestWasmQuery() { + var payload types.QueryMsg + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, goapi wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) ([]byte, uint64, error) { + // Ensure GoAPI is set + suite.Require().NotNil(goapi.CanonicalAddress) + suite.Require().NotNil(goapi.HumanAddress) + + resp, err := json.Marshal(types.StatusResult{Status: exported.Frozen.String()}) + suite.Require().NoError(err) + + return resp, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + }, + { + "failure: contract returns error", + func() { + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) ([]byte, uint64, error) { + return nil, wasmtesting.DefaultGasUsed, wasmtesting.ErrMockContract + }) + }, + types.ErrWasmContractCallFailed, + }, + { + "failure: response fails to unmarshal", + func() { + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) ([]byte, uint64, error) { + return []byte("invalid json"), wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmInvalidResponseData, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + + clientState := endpoint.GetClientState() + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) + + wasmClientState, ok := clientState.(*types.ClientState) + suite.Require().True(ok) + + payload = types.QueryMsg{Status: &types.StatusMsg{}} + + tc.malleate() + + res, err := types.WasmQuery[types.StatusResult](suite.chainA.GetContext(), clientStore, wasmClientState, payload) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + suite.Require().NotNil(res) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} + +func (suite *TypesTestSuite) TestWasmSudo() { + var payload types.SudoMsg + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, goapi wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + // Ensure GoAPI is set + suite.Require().NotNil(goapi.CanonicalAddress) + suite.Require().NotNil(goapi.HumanAddress) + + resp, err := json.Marshal(types.UpdateStateResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.Response{Data: resp}, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + }, + { + "failure: contract returns error", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + return nil, wasmtesting.DefaultGasUsed, wasmtesting.ErrMockContract + }) + }, + types.ErrWasmContractCallFailed, + }, + { + "failure: contract returns non-empty messages", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + resp := wasmvmtypes.Response{Messages: []wasmvmtypes.SubMsg{{}}} + + return &resp, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmSubMessagesNotAllowed, + }, + { + "failure: contract returns non-empty events", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + resp := wasmvmtypes.Response{Events: []wasmvmtypes.Event{{}}} + + return &resp, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmEventsNotAllowed, + }, + { + "failure: contract returns non-empty attributes", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + resp := wasmvmtypes.Response{Attributes: []wasmvmtypes.EventAttribute{{}}} + + return &resp, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmAttributesNotAllowed, + }, + { + "failure: response fails to unmarshal", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + return &wasmvmtypes.Response{Data: []byte("invalid json")}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmInvalidResponseData, + }, + { + "failure: invalid clientstate type", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + newClientState := localhost.NewClientState(clienttypes.NewHeight(1, 1)) + store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), newClientState)) + + resp, err := json.Marshal(types.UpdateStateResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.Response{Data: resp}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmInvalidContractModification, + }, + { + "failure: unmarshallable clientstate bytes", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + store.Set(host.ClientStateKey(), []byte("invalid json")) + + resp, err := json.Marshal(types.UpdateStateResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.Response{Data: resp}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmInvalidContractModification, + }, + { + "failure: delete clientstate", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + store.Delete(host.ClientStateKey()) + + resp, err := json.Marshal(types.UpdateStateResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.Response{Data: resp}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmInvalidContractModification, + }, + { + "failure: change checksum", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + clientState := suite.chainA.GetClientState(defaultWasmClientID) + clientState.(*types.ClientState).Checksum = []byte("new checksum") + store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), clientState)) + + resp, err := json.Marshal(types.UpdateStateResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.Response{Data: resp}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmInvalidContractModification, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + + clientState := endpoint.GetClientState() + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) + + wasmClientState, ok := clientState.(*types.ClientState) + suite.Require().True(ok) + + payload = types.SudoMsg{UpdateState: &types.UpdateStateMsg{}} + + tc.malleate() + + res, err := types.WasmSudo[types.UpdateStateResult](suite.chainA.GetContext(), suite.chainA.App.AppCodec(), clientStore, wasmClientState, payload) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + suite.Require().NotNil(res) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} diff --git a/modules/light-clients/08-wasm/types/wasm.go b/modules/light-clients/08-wasm/types/wasm.go new file mode 100644 index 00000000000..fa6e3d38c3f --- /dev/null +++ b/modules/light-clients/08-wasm/types/wasm.go @@ -0,0 +1,42 @@ +package types + +import ( + "context" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" +) + +// Checksum is a type alias used for wasm byte code checksums. +type Checksum []byte + +// GetAllChecksums is a helper to get all checksums from the store. +// It returns an empty slice if no checksums are found +func GetAllChecksums(ctx context.Context) ([]Checksum, error) { + iterator, err := ibcwasm.Checksums.Iterate(ctx, nil) + if err != nil { + return nil, err + } + + keys, err := iterator.Keys() + if err != nil { + return nil, err + } + + checksums := []Checksum{} + for _, key := range keys { + checksums = append(checksums, key) + } + + return checksums, nil +} + +// HasChecksum returns true if the given checksum exists in the store and +// false otherwise. +func HasChecksum(ctx context.Context, checksum Checksum) bool { + found, err := ibcwasm.Checksums.Has(ctx, checksum) + if err != nil { + return false + } + + return found +} diff --git a/modules/light-clients/08-wasm/types/wasm.pb.go b/modules/light-clients/08-wasm/types/wasm.pb.go new file mode 100644 index 00000000000..a1250579f3c --- /dev/null +++ b/modules/light-clients/08-wasm/types/wasm.pb.go @@ -0,0 +1,752 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/lightclients/wasm/v1/wasm.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + types "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Wasm light client's Client state +type ClientState struct { + // bytes encoding the client state of the underlying light client + // implemented as a Wasm contract. + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + Checksum []byte `protobuf:"bytes,2,opt,name=checksum,proto3" json:"checksum,omitempty"` + LatestHeight types.Height `protobuf:"bytes,3,opt,name=latest_height,json=latestHeight,proto3" json:"latest_height"` +} + +func (m *ClientState) Reset() { *m = ClientState{} } +func (m *ClientState) String() string { return proto.CompactTextString(m) } +func (*ClientState) ProtoMessage() {} +func (*ClientState) Descriptor() ([]byte, []int) { + return fileDescriptor_678928ebbdee1807, []int{0} +} +func (m *ClientState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ClientState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ClientState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ClientState) XXX_Merge(src proto.Message) { + xxx_messageInfo_ClientState.Merge(m, src) +} +func (m *ClientState) XXX_Size() int { + return m.Size() +} +func (m *ClientState) XXX_DiscardUnknown() { + xxx_messageInfo_ClientState.DiscardUnknown(m) +} + +var xxx_messageInfo_ClientState proto.InternalMessageInfo + +// Wasm light client's ConsensusState +type ConsensusState struct { + // bytes encoding the consensus state of the underlying light client + // implemented as a Wasm contract. + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` +} + +func (m *ConsensusState) Reset() { *m = ConsensusState{} } +func (m *ConsensusState) String() string { return proto.CompactTextString(m) } +func (*ConsensusState) ProtoMessage() {} +func (*ConsensusState) Descriptor() ([]byte, []int) { + return fileDescriptor_678928ebbdee1807, []int{1} +} +func (m *ConsensusState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ConsensusState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ConsensusState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ConsensusState) XXX_Merge(src proto.Message) { + xxx_messageInfo_ConsensusState.Merge(m, src) +} +func (m *ConsensusState) XXX_Size() int { + return m.Size() +} +func (m *ConsensusState) XXX_DiscardUnknown() { + xxx_messageInfo_ConsensusState.DiscardUnknown(m) +} + +var xxx_messageInfo_ConsensusState proto.InternalMessageInfo + +// Wasm light client message (either header(s) or misbehaviour) +type ClientMessage struct { + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` +} + +func (m *ClientMessage) Reset() { *m = ClientMessage{} } +func (m *ClientMessage) String() string { return proto.CompactTextString(m) } +func (*ClientMessage) ProtoMessage() {} +func (*ClientMessage) Descriptor() ([]byte, []int) { + return fileDescriptor_678928ebbdee1807, []int{2} +} +func (m *ClientMessage) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ClientMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ClientMessage.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ClientMessage) XXX_Merge(src proto.Message) { + xxx_messageInfo_ClientMessage.Merge(m, src) +} +func (m *ClientMessage) XXX_Size() int { + return m.Size() +} +func (m *ClientMessage) XXX_DiscardUnknown() { + xxx_messageInfo_ClientMessage.DiscardUnknown(m) +} + +var xxx_messageInfo_ClientMessage proto.InternalMessageInfo + +func init() { + proto.RegisterType((*ClientState)(nil), "ibc.lightclients.wasm.v1.ClientState") + proto.RegisterType((*ConsensusState)(nil), "ibc.lightclients.wasm.v1.ConsensusState") + proto.RegisterType((*ClientMessage)(nil), "ibc.lightclients.wasm.v1.ClientMessage") +} + +func init() { + proto.RegisterFile("ibc/lightclients/wasm/v1/wasm.proto", fileDescriptor_678928ebbdee1807) +} + +var fileDescriptor_678928ebbdee1807 = []byte{ + // 316 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x91, 0xb1, 0x4a, 0x03, 0x31, + 0x1c, 0xc6, 0x2f, 0x5a, 0x44, 0xd2, 0xd6, 0xe1, 0x70, 0x38, 0x6e, 0x48, 0x4b, 0x5d, 0xaa, 0xd0, + 0xc4, 0xea, 0x22, 0xe2, 0xd4, 0x22, 0xb8, 0xb8, 0x54, 0x70, 0x70, 0x91, 0x5c, 0x1a, 0x72, 0xc1, + 0xbb, 0xa6, 0xf4, 0x9f, 0xab, 0xf8, 0x06, 0xe2, 0xe4, 0x23, 0xf8, 0x38, 0x1d, 0x3b, 0x3a, 0x89, + 0xb4, 0x2f, 0x22, 0x49, 0xaa, 0xb8, 0xe8, 0x74, 0xdf, 0x7d, 0xf9, 0xe5, 0xff, 0xff, 0xc8, 0x87, + 0x0f, 0x74, 0x26, 0x58, 0xa1, 0x55, 0x6e, 0x45, 0xa1, 0xe5, 0xc4, 0x02, 0x7b, 0xe4, 0x50, 0xb2, + 0x79, 0xdf, 0x7f, 0xe9, 0x74, 0x66, 0xac, 0x89, 0x13, 0x9d, 0x09, 0xfa, 0x1b, 0xa2, 0xfe, 0x70, + 0xde, 0x4f, 0xf7, 0x95, 0x51, 0xc6, 0x43, 0xcc, 0xa9, 0xc0, 0xa7, 0x2d, 0x37, 0x54, 0x98, 0x99, + 0x64, 0x81, 0x77, 0xe3, 0x82, 0x0a, 0x40, 0xe7, 0x05, 0xe1, 0xfa, 0xd0, 0x1b, 0x37, 0x96, 0x5b, + 0x19, 0xc7, 0xb8, 0x36, 0xe6, 0x96, 0x27, 0xa8, 0x8d, 0xba, 0x8d, 0x91, 0xd7, 0x71, 0x8a, 0x77, + 0x45, 0x2e, 0xc5, 0x03, 0x54, 0x65, 0xb2, 0xe5, 0xfd, 0x9f, 0xff, 0xf8, 0x12, 0x37, 0x0b, 0x6e, + 0x25, 0xd8, 0xfb, 0x5c, 0xba, 0x58, 0xc9, 0x76, 0x1b, 0x75, 0xeb, 0x27, 0x29, 0x75, 0x41, 0xdd, + 0x62, 0xba, 0x59, 0x37, 0xef, 0xd3, 0x2b, 0x4f, 0x0c, 0x6a, 0x8b, 0x8f, 0x56, 0x34, 0x6a, 0x84, + 0x6b, 0xc1, 0x3b, 0xaf, 0x3d, 0xbf, 0xb5, 0xa2, 0xce, 0x11, 0xde, 0x1b, 0x9a, 0x09, 0xc8, 0x09, + 0x54, 0xf0, 0x67, 0x9c, 0x0d, 0x7b, 0x88, 0x9b, 0x21, 0xf7, 0xb5, 0x04, 0xe0, 0xea, 0x1f, 0x74, + 0x70, 0xbb, 0x58, 0x11, 0xb4, 0x5c, 0x11, 0xf4, 0xb9, 0x22, 0xe8, 0x75, 0x4d, 0xa2, 0xe5, 0x9a, + 0x44, 0xef, 0x6b, 0x12, 0xdd, 0x5d, 0x28, 0x6d, 0xf3, 0x2a, 0xa3, 0xc2, 0x94, 0x4c, 0x18, 0x28, + 0x0d, 0x30, 0x9d, 0x89, 0x9e, 0x32, 0xac, 0x34, 0xe3, 0xaa, 0x90, 0x10, 0x0a, 0xe9, 0x7d, 0x37, + 0x72, 0x7c, 0xd6, 0xf3, 0xa5, 0xd8, 0xa7, 0xa9, 0x84, 0x6c, 0xc7, 0x3f, 0xe1, 0xe9, 0x57, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xea, 0xf4, 0x2e, 0xa9, 0xba, 0x01, 0x00, 0x00, +} + +func (m *ClientState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ClientState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ClientState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.LatestHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintWasm(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.Checksum) > 0 { + i -= len(m.Checksum) + copy(dAtA[i:], m.Checksum) + i = encodeVarintWasm(dAtA, i, uint64(len(m.Checksum))) + i-- + dAtA[i] = 0x12 + } + if len(m.Data) > 0 { + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarintWasm(dAtA, i, uint64(len(m.Data))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ConsensusState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ConsensusState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ConsensusState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Data) > 0 { + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarintWasm(dAtA, i, uint64(len(m.Data))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ClientMessage) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ClientMessage) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ClientMessage) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Data) > 0 { + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarintWasm(dAtA, i, uint64(len(m.Data))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintWasm(dAtA []byte, offset int, v uint64) int { + offset -= sovWasm(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *ClientState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Data) + if l > 0 { + n += 1 + l + sovWasm(uint64(l)) + } + l = len(m.Checksum) + if l > 0 { + n += 1 + l + sovWasm(uint64(l)) + } + l = m.LatestHeight.Size() + n += 1 + l + sovWasm(uint64(l)) + return n +} + +func (m *ConsensusState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Data) + if l > 0 { + n += 1 + l + sovWasm(uint64(l)) + } + return n +} + +func (m *ClientMessage) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Data) + if l > 0 { + n += 1 + l + sovWasm(uint64(l)) + } + return n +} + +func sovWasm(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozWasm(x uint64) (n int) { + return sovWasm(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *ClientState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWasm + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ClientState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ClientState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWasm + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthWasm + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthWasm + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...) + if m.Data == nil { + m.Data = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Checksum", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWasm + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthWasm + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthWasm + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Checksum = append(m.Checksum[:0], dAtA[iNdEx:postIndex]...) + if m.Checksum == nil { + m.Checksum = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LatestHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWasm + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthWasm + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthWasm + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.LatestHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipWasm(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthWasm + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ConsensusState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWasm + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ConsensusState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ConsensusState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWasm + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthWasm + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthWasm + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...) + if m.Data == nil { + m.Data = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipWasm(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthWasm + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ClientMessage) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWasm + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ClientMessage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ClientMessage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWasm + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthWasm + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthWasm + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...) + if m.Data == nil { + m.Data = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipWasm(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthWasm + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipWasm(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowWasm + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowWasm + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowWasm + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthWasm + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupWasm + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthWasm + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthWasm = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowWasm = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupWasm = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/light-clients/08-wasm/types/wasm_test.go b/modules/light-clients/08-wasm/types/wasm_test.go new file mode 100644 index 00000000000..27325f7822a --- /dev/null +++ b/modules/light-clients/08-wasm/types/wasm_test.go @@ -0,0 +1,125 @@ +package types_test + +import ( + "crypto/sha256" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" +) + +func (suite *TypesTestSuite) TestGetChecksums() { + testCases := []struct { + name string + malleate func() + expResult func(checksums []types.Checksum) + }{ + { + "success: no contract stored.", + func() {}, + func(checksums []types.Checksum) { + suite.Require().Len(checksums, 0) + }, + }, + { + "success: default mock vm contract stored.", + func() { + suite.SetupWasmWithMockVM() + }, + func(checksums []types.Checksum) { + suite.Require().Len(checksums, 1) + expectedChecksum := sha256.Sum256(wasmtesting.Code) + suite.Require().Equal(types.Checksum(expectedChecksum[:]), checksums[0]) + }, + }, + { + "success: non-empty checksums", + func() { + suite.SetupWasmWithMockVM() + + err := ibcwasm.Checksums.Set(suite.chainA.GetContext(), types.Checksum("checksum")) + suite.Require().NoError(err) + }, + func(checksums []types.Checksum) { + suite.Require().Len(checksums, 2) + suite.Require().Contains(checksums, types.Checksum("checksum")) + }, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + tc.malleate() + + checksums, err := types.GetAllChecksums(suite.chainA.GetContext()) + suite.Require().NoError(err) + tc.expResult(checksums) + }) + } +} + +func (suite *TypesTestSuite) TestAddChecksum() { + suite.SetupWasmWithMockVM() + + checksums, err := types.GetAllChecksums(suite.chainA.GetContext()) + suite.Require().NoError(err) + // default mock vm contract is stored + suite.Require().Len(checksums, 1) + + checksum1 := types.Checksum("checksum1") + checksum2 := types.Checksum("checksum2") + err = ibcwasm.Checksums.Set(suite.chainA.GetContext(), checksum1) + suite.Require().NoError(err) + err = ibcwasm.Checksums.Set(suite.chainA.GetContext(), checksum2) + suite.Require().NoError(err) + + // Test adding the same checksum twice + err = ibcwasm.Checksums.Set(suite.chainA.GetContext(), checksum1) + suite.Require().NoError(err) + + checksums, err = types.GetAllChecksums(suite.chainA.GetContext()) + suite.Require().NoError(err) + suite.Require().Len(checksums, 3) + suite.Require().Contains(checksums, checksum1) + suite.Require().Contains(checksums, checksum2) +} + +func (suite *TypesTestSuite) TestHasChecksum() { + var checksum types.Checksum + + testCases := []struct { + name string + malleate func() + exprResult bool + }{ + { + "success: checksum exists", + func() { + checksum = types.Checksum("checksum") + err := ibcwasm.Checksums.Set(suite.chainA.GetContext(), checksum) + suite.Require().NoError(err) + }, + true, + }, + { + "success: checksum does not exist", + func() { + checksum = types.Checksum("non-existent-checksum") + }, + false, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + tc.malleate() + + result := types.HasChecksum(suite.chainA.GetContext(), checksum) + suite.Require().Equal(tc.exprResult, result) + }) + } +} diff --git a/proto/ibc/lightclients/wasm/v1/genesis.proto b/proto/ibc/lightclients/wasm/v1/genesis.proto new file mode 100644 index 00000000000..637ba1677e3 --- /dev/null +++ b/proto/ibc/lightclients/wasm/v1/genesis.proto @@ -0,0 +1,20 @@ + +syntax = "proto3"; +package ibc.lightclients.wasm.v1; + +import "gogoproto/gogo.proto"; + +option go_package = "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types"; + +// GenesisState defines 08-wasm's keeper genesis state +message GenesisState { + // uploaded light client wasm contracts + repeated Contract contracts = 1 [(gogoproto.nullable) = false]; +} + +// Contract stores contract code +message Contract { + option (gogoproto.goproto_getters) = false; + // contract byte code + bytes code_bytes = 1; +} \ No newline at end of file diff --git a/proto/ibc/lightclients/wasm/v1/query.proto b/proto/ibc/lightclients/wasm/v1/query.proto new file mode 100644 index 00000000000..bc6650a1de2 --- /dev/null +++ b/proto/ibc/lightclients/wasm/v1/query.proto @@ -0,0 +1,46 @@ +syntax = "proto3"; +package ibc.lightclients.wasm.v1; + +import "google/api/annotations.proto"; +import "cosmos/base/query/v1beta1/pagination.proto"; + +option go_package = "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types"; + +// Query service for wasm module +service Query { + // Get all Wasm checksums + rpc Checksums(QueryChecksumsRequest) returns (QueryChecksumsResponse) { + option (google.api.http).get = "/ibc/lightclients/wasm/v1/checksums"; + } + + // Get Wasm code for given code hash + rpc Code(QueryCodeRequest) returns (QueryCodeResponse) { + option (google.api.http).get = "/ibc/lightclients/wasm/v1/checksums/{checksum}/code"; + } +} + +// QueryChecksumsRequest is the request type for the Query/Checksums RPC method. +message QueryChecksumsRequest { + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 1; +} + +// QueryChecksumsResponse is the response type for the Query/Checksums RPC method. +message QueryChecksumsResponse { + // checksums is a list of the hex encoded checksums of all wasm codes stored. + repeated string checksums = 1; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryCodeRequest is the request type for the Query/Code RPC method. +message QueryCodeRequest { + // checksum is a hex encoded string of the code stored. + string checksum = 1; +} + +// QueryCodeResponse is the response type for the Query/Code RPC method. +message QueryCodeResponse { + bytes data = 1; +} diff --git a/proto/ibc/lightclients/wasm/v1/tx.proto b/proto/ibc/lightclients/wasm/v1/tx.proto new file mode 100644 index 00000000000..d2fc465919c --- /dev/null +++ b/proto/ibc/lightclients/wasm/v1/tx.proto @@ -0,0 +1,66 @@ +syntax = "proto3"; +package ibc.lightclients.wasm.v1; + +option go_package = "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types"; + +import "cosmos/msg/v1/msg.proto"; + +// Msg defines the ibc/08-wasm Msg service. +service Msg { + option (cosmos.msg.v1.service) = true; + + // StoreCode defines a rpc handler method for MsgStoreCode. + rpc StoreCode(MsgStoreCode) returns (MsgStoreCodeResponse); + + // RemoveChecksum defines a rpc handler method for MsgRemoveChecksum. + rpc RemoveChecksum(MsgRemoveChecksum) returns (MsgRemoveChecksumResponse); + + // MigrateContract defines a rpc handler method for MsgMigrateContract. + rpc MigrateContract(MsgMigrateContract) returns (MsgMigrateContractResponse); +} + +// MsgStoreCode defines the request type for the StoreCode rpc. +message MsgStoreCode { + option (cosmos.msg.v1.signer) = "signer"; + + // signer address + string signer = 1; + // wasm byte code of light client contract. It can be raw or gzip compressed + bytes wasm_byte_code = 2; +} + +// MsgStoreCodeResponse defines the response type for the StoreCode rpc +message MsgStoreCodeResponse { + // checksum is the sha256 hash of the stored code + bytes checksum = 1; +} + +// MsgRemoveChecksum defines the request type for the MsgRemoveChecksum rpc. +message MsgRemoveChecksum { + option (cosmos.msg.v1.signer) = "signer"; + + // signer address + string signer = 1; + // checksum is the sha256 hash to be removed from the store + bytes checksum = 2; +} + +// MsgStoreChecksumResponse defines the response type for the StoreCode rpc +message MsgRemoveChecksumResponse {} + +// MsgMigrateContract defines the request type for the MigrateContract rpc. +message MsgMigrateContract { + option (cosmos.msg.v1.signer) = "signer"; + + // signer address + string signer = 1; + // the client id of the contract + string client_id = 2; + // checksum is the sha256 hash of the new wasm byte code for the contract + bytes checksum = 3; + // the json encoded message to be passed to the contract on migration + bytes msg = 4; +} + +// MsgMigrateContractResponse defines the response type for the MigrateContract rpc +message MsgMigrateContractResponse {} diff --git a/proto/ibc/lightclients/wasm/v1/wasm.proto b/proto/ibc/lightclients/wasm/v1/wasm.proto new file mode 100644 index 00000000000..2abbc55522f --- /dev/null +++ b/proto/ibc/lightclients/wasm/v1/wasm.proto @@ -0,0 +1,33 @@ + +syntax = "proto3"; +package ibc.lightclients.wasm.v1; + +import "gogoproto/gogo.proto"; +import "ibc/core/client/v1/client.proto"; + +option go_package = "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types"; + +// Wasm light client's Client state +message ClientState { + option (gogoproto.goproto_getters) = false; + // bytes encoding the client state of the underlying light client + // implemented as a Wasm contract. + bytes data = 1; + bytes checksum = 2; + ibc.core.client.v1.Height latest_height = 3 [(gogoproto.nullable) = false]; +} + +// Wasm light client's ConsensusState +message ConsensusState { + option (gogoproto.goproto_getters) = false; + // bytes encoding the consensus state of the underlying light client + // implemented as a Wasm contract. + bytes data = 1; +} + +// Wasm light client message (either header(s) or misbehaviour) +message ClientMessage { + option (gogoproto.goproto_getters) = false; + + bytes data = 1; +} diff --git a/scripts/go-lint-all.sh b/scripts/go-lint-all.sh index 7a7883b36aa..a6811de23b7 100755 --- a/scripts/go-lint-all.sh +++ b/scripts/go-lint-all.sh @@ -9,10 +9,10 @@ lint_module() { local root="$1" shift cd "$(dirname "$root")" && - echo "linting $(grep "^module" go.mod) [$(date -Iseconds -u)]" && + echo "linting $(grep "^module" go.mod) [$(date -u +"%Y-%m-%dT%H:%M:%S")]" && golangci-lint run ./... -c "${REPO_ROOT}/.golangci.yml" "$@" } export -f lint_module find "${REPO_ROOT}" -type f -name go.mod -print0 | - xargs -0 -I{} bash -c 'lint_module "$@"' _ {} "$@" \ No newline at end of file + xargs -0 -I{} bash -c 'lint_module "$@"' _ {} "$@" diff --git a/scripts/go-mod-tidy-all.sh b/scripts/go-mod-tidy-all.sh new file mode 100755 index 00000000000..c129f734d44 --- /dev/null +++ b/scripts/go-mod-tidy-all.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -euo pipefail + +for modfile in $(find . -name go.mod); do + echo "Updating $modfile" + DIR=$(dirname $modfile) + (cd $DIR; go mod tidy) +done diff --git a/testing/chain.go b/testing/chain.go index 01dcd491855..77580935e57 100644 --- a/testing/chain.go +++ b/testing/chain.go @@ -475,6 +475,7 @@ func (chain *TestChain) ConstructUpdateTMClientHeaderWithTrustedHeight(counterpa if err != nil { return nil, err } + trustedVals.TotalVotingPower = tmTrustedVals.TotalVotingPower() header.TrustedValidators = trustedVals return header, nil @@ -550,11 +551,13 @@ func (chain *TestChain) CreateTMClientHeader(chainID string, blockHeight int64, if cmtValSet != nil { //nolint:staticcheck valSet, err = cmtValSet.ToProto() require.NoError(chain.TB, err) + valSet.TotalVotingPower = cmtValSet.TotalVotingPower() } if tmTrustedVals != nil { trustedVals, err = tmTrustedVals.ToProto() require.NoError(chain.TB, err) + trustedVals.TotalVotingPower = tmTrustedVals.TotalVotingPower() } // The trusted fields may be nil. They may be filled before relaying messages to a client. diff --git a/testing/endpoint.go b/testing/endpoint.go index 9e7363a6c4a..76ba78fe77e 100644 --- a/testing/endpoint.go +++ b/testing/endpoint.go @@ -104,7 +104,6 @@ func (endpoint *Endpoint) CreateClient() (err error) { // solo := NewSolomachine(endpoint.Chain.TB, endpoint.Chain.Codec, clientID, "", 1) // clientState = solo.ClientState() // consensusState = solo.ConsensusState() - default: err = fmt.Errorf("client type %s is not supported", endpoint.ClientConfig.GetClientType()) } @@ -139,7 +138,6 @@ func (endpoint *Endpoint) UpdateClient() (err error) { switch endpoint.ClientConfig.GetClientType() { case exported.Tendermint: header, err = endpoint.Chain.ConstructUpdateTMClientHeader(endpoint.Counterparty.Chain, endpoint.ClientID) - default: err = fmt.Errorf("client type %s is not supported", endpoint.ClientConfig.GetClientType()) } @@ -167,11 +165,11 @@ func (endpoint *Endpoint) UpgradeChain() error { return fmt.Errorf("cannot upgrade chain if there is no counterparty client") } - clientState := endpoint.Counterparty.GetClientState().(*ibctm.ClientState) + clientState := endpoint.Counterparty.GetClientState() + tmClientState := clientState.(*ibctm.ClientState) // increment revision number in chainID - - oldChainID := clientState.ChainId + oldChainID := tmClientState.ChainId if !clienttypes.IsRevisionFormat(oldChainID) { return fmt.Errorf("cannot upgrade chain which is not of revision format: %s", oldChainID) } @@ -183,22 +181,23 @@ func (endpoint *Endpoint) UpgradeChain() error { } // update chain - baseapp.SetChainID(newChainID)(endpoint.Chain.GetSimApp().GetBaseApp()) + baseapp.SetChainID(newChainID)(endpoint.Chain.App.GetBaseApp()) endpoint.Chain.ChainID = newChainID endpoint.Chain.CurrentHeader.ChainID = newChainID endpoint.Chain.NextBlock() // commit changes // update counterparty client manually - clientState.ChainId = newChainID - clientState.LatestHeight = clienttypes.NewHeight(revisionNumber+1, clientState.LatestHeight.GetRevisionHeight()+1) + tmClientState.ChainId = newChainID + tmClientState.LatestHeight = clienttypes.NewHeight(revisionNumber+1, tmClientState.LatestHeight.GetRevisionHeight()+1) + endpoint.Counterparty.SetClientState(clientState) - consensusState := &ibctm.ConsensusState{ + tmConsensusState := &ibctm.ConsensusState{ Timestamp: endpoint.Chain.LastHeader.GetTime(), Root: commitmenttypes.NewMerkleRoot(endpoint.Chain.LastHeader.Header.GetAppHash()), NextValidatorsHash: endpoint.Chain.LastHeader.Header.NextValidatorsHash, } - endpoint.Counterparty.SetConsensusState(consensusState, clientState.GetLatestHeight()) + endpoint.Counterparty.SetConsensusState(tmConsensusState, clientState.GetLatestHeight()) // ensure the next update isn't identical to the one set in state endpoint.Chain.Coordinator.IncrementTime() diff --git a/testing/mock/mock.go b/testing/mock/mock.go index e73be083edf..96da67a25c7 100644 --- a/testing/mock/mock.go +++ b/testing/mock/mock.go @@ -65,6 +65,12 @@ type PortKeeper interface { // AppModuleBasic is the mock AppModuleBasic. type AppModuleBasic struct{} +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (AppModuleBasic) IsOnePerModuleType() {} + +// IsAppModule implements the appmodule.AppModule interface. +func (AppModuleBasic) IsAppModule() {} + // Name implements AppModuleBasic interface. func (AppModuleBasic) Name() string { return ModuleName @@ -163,3 +169,10 @@ func (KeyPath) String() string { func (KeyPath) Empty() bool { return false } + +var _ exported.Height = Height{} + +// Height defines a placeholder struct which implements the exported.Height interface +type Height struct { + exported.Height +}