Skip to content

Commit

Permalink
feat: allow module safe queries in ICA (#5785)
Browse files Browse the repository at this point in the history
* imp: initial modification of tx.proto

* imp: ran 'make proto-all'

* fix: compiler errors

* imp: added query router interface

* imp: added queryRouter to icahost keeper

* imp: improved proto definitions

* imp: ran 'make proto-all'

* imp: added sdk.Msg helpers

* feat: basic implementation

* style: improved field names

* imp: ran 'make proto-all'

* fix: compiler errors

* imp: ran gofumpt

* feat: tests passing

* feat: tests improved

* test: removed unneeded code

* imp: improved 'IsModuleSafe' function

* imp: added IsModuleQuerySafe to msg_server

* imp: added more test cases

* fix: callbacks compiler

* fix: non determinancy issues

* fix: added query msg to codec

* imp: whitelist logic added

* e2e: new test added

* fix: new test

* fix: test

* fix: e2e

* fix: e2e

* imp(e2e): added the QueryTxsByEvents function

* fix: e2e

* e2e: lint fix

* fix: e2e

* e2e: debug

* fix: e2e

* test: debug helpers

* debug

* test: added codec_test case

* imp: additional test case

* imp: added important unit test

* r4r

* e2e: debug

* imp: added logs

* fix: e2e

* fix: e2e

* fix: e2e

* imp: added height to proto response

* imp: ran 'make proto-all'

* imp: added height

* e2e: updated e2e to test height

* imp: review suggestions

* e2e: remove unneeded log

* refactor: refactored 'ExtractValueFromEvents'

* e2e: review item

* imp: review item

* nit: review item

* docs: added godocs

* test: unit test for mqsWhitelist added

* imp: added logging

* style: rename to allow list

* add changelog

---------

Co-authored-by: Carlos Rodriguez <[email protected]>
(cherry picked from commit eecfa5c)

# Conflicts:
#	e2e/testsuite/codec.go
#	e2e/testsuite/tx.go
#	modules/apps/27-interchain-accounts/host/keeper/keeper.go
#	modules/apps/27-interchain-accounts/host/keeper/keeper_test.go
#	modules/apps/27-interchain-accounts/host/keeper/msg_server.go
#	modules/apps/27-interchain-accounts/host/keeper/msg_server_test.go
#	modules/apps/27-interchain-accounts/host/types/codec.go
#	modules/apps/27-interchain-accounts/host/types/codec_test.go
#	modules/apps/27-interchain-accounts/host/types/host.pb.go
#	modules/apps/27-interchain-accounts/host/types/msgs.go
#	modules/apps/27-interchain-accounts/host/types/tx.pb.go
#	modules/apps/callbacks/testing/simapp/app.go
#	modules/light-clients/08-wasm/testing/simapp/app.go
#	proto/ibc/applications/interchain_accounts/host/v1/tx.proto
#	testing/simapp/app.go
  • Loading branch information
srdtrk authored and mergify[bot] committed Apr 9, 2024
1 parent 885fdce commit b90f5c2
Show file tree
Hide file tree
Showing 21 changed files with 3,809 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ Ref: https://keepachangelog.com/en/1.0.0/

### Features

* (apps/27-interchain-accounts) [\#5785](https://github.com/cosmos/ibc-go/pull/5785) Introduce a new tx message that ICA host submodule can use to query the chain (only those marked with `module_query_safe`) and write the responses to the acknowledgement.

### Bug Fixes

## [v7.4.0](https://github.com/cosmos/ibc-go/releases/tag/v7.4.0) - 2024-04-05
Expand Down
161 changes: 161 additions & 0 deletions e2e/tests/interchain_accounts/query_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
//go:build !test_e2e

package interchainaccounts

import (
"context"
"encoding/hex"
"encoding/json"
"testing"
"time"

"github.com/cosmos/gogoproto/proto"
"github.com/strangelove-ventures/interchaintest/v8/testutil"
testifysuite "github.com/stretchr/testify/suite"

sdk "github.com/cosmos/cosmos-sdk/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"

"github.com/cosmos/ibc-go/e2e/testsuite"
"github.com/cosmos/ibc-go/e2e/testvalues"
controllertypes "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/controller/types"
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"
channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types"
ibctesting "github.com/cosmos/ibc-go/v8/testing"
)

func TestInterchainAccountsQueryTestSuite(t *testing.T) {
testifysuite.Run(t, new(InterchainAccountsQueryTestSuite))
}

type InterchainAccountsQueryTestSuite struct {
testsuite.E2ETestSuite
}

func (s *InterchainAccountsQueryTestSuite) TestInterchainAccountsQuery() {
t := s.T()
ctx := context.TODO()

// setup relayers and connection-0 between two chains
// channel-0 is a transfer channel but it will not be used in this test case
relayer, _ := s.SetupChainsRelayerAndChannel(ctx, nil)
chainA, chainB := s.GetChains()

// setup 2 accounts: controller account on chain A, a second chain B account.
// host account will be created when the ICA is registered
controllerAccount := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount)
controllerAddress := controllerAccount.FormattedAddress()
chainBAccount := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount)
var hostAccount string

t.Run("broadcast MsgRegisterInterchainAccount", func(t *testing.T) {
// explicitly set the version string because we don't want to use incentivized channels.
version := icatypes.NewDefaultMetadataString(ibctesting.FirstConnectionID, ibctesting.FirstConnectionID)
msgRegisterAccount := controllertypes.NewMsgRegisterInterchainAccount(ibctesting.FirstConnectionID, controllerAddress, version, channeltypes.UNORDERED)

txResp := s.BroadcastMessages(ctx, chainA, controllerAccount, msgRegisterAccount)
s.AssertTxSuccess(txResp)
})

t.Run("start relayer", func(t *testing.T) {
s.StartRelayer(relayer)
})

t.Run("verify interchain account", func(t *testing.T) {
var err error
hostAccount, err = s.QueryInterchainAccount(ctx, chainA, controllerAddress, ibctesting.FirstConnectionID)
s.Require().NoError(err)
s.Require().NotZero(len(hostAccount))

channels, err := relayer.GetChannels(ctx, s.GetRelayerExecReporter(), chainA.Config().ChainID)
s.Require().NoError(err)
s.Require().Len(channels, 2)
})

t.Run("query via interchain account", func(t *testing.T) {
// the host account need not be funded
t.Run("broadcast query packet", func(t *testing.T) {
balanceQuery := banktypes.NewQueryBalanceRequest(chainBAccount.Address(), chainB.Config().Denom)
queryBz, err := balanceQuery.Marshal()
s.Require().NoError(err)

queryMsg := icahosttypes.NewMsgModuleQuerySafe(hostAccount, []*icahosttypes.QueryRequest{
{
Path: "/cosmos.bank.v1beta1.Query/Balance",
Data: queryBz,
},
})

cdc := testsuite.Codec()
bz, err := icatypes.SerializeCosmosTx(cdc, []proto.Message{queryMsg}, icatypes.EncodingProtobuf)
s.Require().NoError(err)

packetData := icatypes.InterchainAccountPacketData{
Type: icatypes.EXECUTE_TX,
Data: bz,
Memo: "e2e",
}

icaQueryMsg := controllertypes.NewMsgSendTx(controllerAddress, ibctesting.FirstConnectionID, uint64(time.Hour.Nanoseconds()), packetData)

txResp := s.BroadcastMessages(ctx, chainA, controllerAccount, icaQueryMsg)
s.AssertTxSuccess(txResp)

s.Require().NoError(testutil.WaitForBlocks(ctx, 10, chainA, chainB))
})

t.Run("verify query response", func(t *testing.T) {
var expQueryHeight uint64

ack := &channeltypes.Acknowledgement_Result{}
t.Run("retrieve acknowledgement", func(t *testing.T) {
txSearchRes, err := s.QueryTxsByEvents(
ctx, chainB, 1, 1,
"message.action='/ibc.core.channel.v1.MsgRecvPacket'", "",
)
s.Require().NoError(err)
s.Require().Len(txSearchRes.Txs, 1)

expQueryHeight = uint64(txSearchRes.Txs[0].Height)

ackHexValue, isFound := s.ExtractValueFromEvents(
txSearchRes.Txs[0].Events,
channeltypes.EventTypeWriteAck,
channeltypes.AttributeKeyAckHex,
)
s.Require().True(isFound)
s.Require().NotEmpty(ackHexValue)

ackBz, err := hex.DecodeString(ackHexValue)
s.Require().NoError(err)

err = json.Unmarshal(ackBz, ack)
s.Require().NoError(err)
})

icaAck := &sdk.TxMsgData{}
t.Run("unmarshal ica response", func(t *testing.T) {
err := proto.Unmarshal(ack.Result, icaAck)
s.Require().NoError(err)
s.Require().Len(icaAck.GetMsgResponses(), 1)
})

queryTxResp := &icahosttypes.MsgModuleQuerySafeResponse{}
t.Run("unmarshal MsgModuleQuerySafeResponse", func(t *testing.T) {
err := proto.Unmarshal(icaAck.MsgResponses[0].Value, queryTxResp)
s.Require().NoError(err)
s.Require().Len(queryTxResp.Responses, 1)
s.Require().Equal(expQueryHeight, queryTxResp.Height)
})

balanceResp := &banktypes.QueryBalanceResponse{}
t.Run("unmarshal and verify bank query response", func(t *testing.T) {
err := proto.Unmarshal(queryTxResp.Responses[0], balanceResp)
s.Require().NoError(err)
s.Require().Equal(chainB.Config().Denom, balanceResp.Balance.Denom)
s.Require().Equal(testvalues.StartingTokenAmount, balanceResp.Balance.Amount.Int64())
})
})
})
}
136 changes: 136 additions & 0 deletions e2e/testsuite/codec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package testsuite

import (
"bytes"
"encoding/hex"

"github.com/cosmos/gogoproto/jsonpb"
"github.com/cosmos/gogoproto/proto"

upgradetypes "cosmossdk.io/x/upgrade/types"

Check failure on line 10 in e2e/testsuite/codec.go

View workflow job for this annotation

GitHub Actions / build (amd64)

cannot find module providing package cosmossdk.io/x/upgrade/types: import lookup disabled by -mod=readonly

Check failure on line 10 in e2e/testsuite/codec.go

View workflow job for this annotation

GitHub Actions / build (arm)

cannot find module providing package cosmossdk.io/x/upgrade/types: import lookup disabled by -mod=readonly

"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module/testutil"
txtypes "github.com/cosmos/cosmos-sdk/types/tx"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/authz"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
grouptypes "github.com/cosmos/cosmos-sdk/x/group"
proposaltypes "github.com/cosmos/cosmos-sdk/x/params/types/proposal"

wasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types"

Check failure on line 26 in e2e/testsuite/codec.go

View workflow job for this annotation

GitHub Actions / build (amd64)

cannot find module providing package github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types: import lookup disabled by -mod=readonly

Check failure on line 26 in e2e/testsuite/codec.go

View workflow job for this annotation

GitHub Actions / build (arm)

cannot find module providing package github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types: import lookup disabled by -mod=readonly
icacontrollertypes "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/controller/types"

Check failure on line 27 in e2e/testsuite/codec.go

View workflow job for this annotation

GitHub Actions / build (amd64)

cannot find module providing package github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/controller/types: import lookup disabled by -mod=readonly

Check failure on line 27 in e2e/testsuite/codec.go

View workflow job for this annotation

GitHub Actions / build (arm)

cannot find module providing package github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/controller/types: import lookup disabled by -mod=readonly
icahosttypes "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/host/types"

Check failure on line 28 in e2e/testsuite/codec.go

View workflow job for this annotation

GitHub Actions / build (amd64)

cannot find module providing package github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/host/types: import lookup disabled by -mod=readonly

Check failure on line 28 in e2e/testsuite/codec.go

View workflow job for this annotation

GitHub Actions / build (arm)

cannot find module providing package github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/host/types: import lookup disabled by -mod=readonly
feetypes "github.com/cosmos/ibc-go/v8/modules/apps/29-fee/types"

Check failure on line 29 in e2e/testsuite/codec.go

View workflow job for this annotation

GitHub Actions / build (amd64)

cannot find module providing package github.com/cosmos/ibc-go/v8/modules/apps/29-fee/types: import lookup disabled by -mod=readonly

Check failure on line 29 in e2e/testsuite/codec.go

View workflow job for this annotation

GitHub Actions / build (arm)

cannot find module providing package github.com/cosmos/ibc-go/v8/modules/apps/29-fee/types: import lookup disabled by -mod=readonly
transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types"

Check failure on line 30 in e2e/testsuite/codec.go

View workflow job for this annotation

GitHub Actions / build (amd64)

cannot find module providing package github.com/cosmos/ibc-go/v8/modules/apps/transfer/types: import lookup disabled by -mod=readonly

Check failure on line 30 in e2e/testsuite/codec.go

View workflow job for this annotation

GitHub Actions / build (arm)

cannot find module providing package github.com/cosmos/ibc-go/v8/modules/apps/transfer/types: import lookup disabled by -mod=readonly
v7migrations "github.com/cosmos/ibc-go/v8/modules/core/02-client/migrations/v7"

Check failure on line 31 in e2e/testsuite/codec.go

View workflow job for this annotation

GitHub Actions / build (amd64)

cannot find module providing package github.com/cosmos/ibc-go/v8/modules/core/02-client/migrations/v7: import lookup disabled by -mod=readonly

Check failure on line 31 in e2e/testsuite/codec.go

View workflow job for this annotation

GitHub Actions / build (arm)

cannot find module providing package github.com/cosmos/ibc-go/v8/modules/core/02-client/migrations/v7: import lookup disabled by -mod=readonly
clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types"

Check failure on line 32 in e2e/testsuite/codec.go

View workflow job for this annotation

GitHub Actions / build (amd64)

cannot find module providing package github.com/cosmos/ibc-go/v8/modules/core/02-client/types: import lookup disabled by -mod=readonly

Check failure on line 32 in e2e/testsuite/codec.go

View workflow job for this annotation

GitHub Actions / build (arm)

cannot find module providing package github.com/cosmos/ibc-go/v8/modules/core/02-client/types: import lookup disabled by -mod=readonly
connectiontypes "github.com/cosmos/ibc-go/v8/modules/core/03-connection/types"
channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types"
solomachine "github.com/cosmos/ibc-go/v8/modules/light-clients/06-solomachine"
ibctmtypes "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint"
localhost "github.com/cosmos/ibc-go/v8/modules/light-clients/09-localhost"
ibctesting "github.com/cosmos/ibc-go/v8/testing"
simappparams "github.com/cosmos/ibc-go/v8/testing/simapp/params"
)

// Codec returns the global E2E protobuf codec.
func Codec() *codec.ProtoCodec {
cdc, _ := codecAndEncodingConfig()
return cdc
}

// EncodingConfig returns the global E2E encoding config.
func EncodingConfig() simappparams.EncodingConfig {
_, cfg := codecAndEncodingConfig()
return cfg
}

// SDKEncodingConfig returns the global E2E encoding config.
func SDKEncodingConfig() *testutil.TestEncodingConfig {
_, cfg := codecAndEncodingConfig()
return &testutil.TestEncodingConfig{
InterfaceRegistry: cfg.InterfaceRegistry,
Codec: cfg.Codec,
TxConfig: cfg.TxConfig,
Amino: cfg.Amino,
}
}

func codecAndEncodingConfig() (*codec.ProtoCodec, simappparams.EncodingConfig) {
cfg := simappparams.MakeTestEncodingConfig()

// ibc types
icacontrollertypes.RegisterInterfaces(cfg.InterfaceRegistry)
icahosttypes.RegisterInterfaces(cfg.InterfaceRegistry)
feetypes.RegisterInterfaces(cfg.InterfaceRegistry)
solomachine.RegisterInterfaces(cfg.InterfaceRegistry)
v7migrations.RegisterInterfaces(cfg.InterfaceRegistry)
transfertypes.RegisterInterfaces(cfg.InterfaceRegistry)
clienttypes.RegisterInterfaces(cfg.InterfaceRegistry)
channeltypes.RegisterInterfaces(cfg.InterfaceRegistry)
connectiontypes.RegisterInterfaces(cfg.InterfaceRegistry)
ibctmtypes.RegisterInterfaces(cfg.InterfaceRegistry)
localhost.RegisterInterfaces(cfg.InterfaceRegistry)
wasmtypes.RegisterInterfaces(cfg.InterfaceRegistry)

// all other types
upgradetypes.RegisterInterfaces(cfg.InterfaceRegistry)
banktypes.RegisterInterfaces(cfg.InterfaceRegistry)
govv1beta1.RegisterInterfaces(cfg.InterfaceRegistry)
govv1.RegisterInterfaces(cfg.InterfaceRegistry)
authtypes.RegisterInterfaces(cfg.InterfaceRegistry)
cryptocodec.RegisterInterfaces(cfg.InterfaceRegistry)
grouptypes.RegisterInterfaces(cfg.InterfaceRegistry)
proposaltypes.RegisterInterfaces(cfg.InterfaceRegistry)
authz.RegisterInterfaces(cfg.InterfaceRegistry)
txtypes.RegisterInterfaces(cfg.InterfaceRegistry)

cdc := codec.NewProtoCodec(cfg.InterfaceRegistry)
return cdc, cfg
}

// UnmarshalMsgResponses attempts to unmarshal the tx msg responses into the provided message types.
func UnmarshalMsgResponses(txResp sdk.TxResponse, msgs ...codec.ProtoMarshaler) error {
cdc := Codec()
bz, err := hex.DecodeString(txResp.Data)
if err != nil {
return err
}

return ibctesting.UnmarshalMsgResponses(cdc, bz, msgs...)
}

// MustProtoMarshalJSON provides an auxiliary function to return Proto3 JSON encoded
// bytes of a message. This function should be used when marshalling a proto.Message
// from the e2e tests. This function strips out unknown fields. This is useful for
// backwards compatibility tests where the the types imported by the e2e package have
// new fields that older versions do not recognize.
func MustProtoMarshalJSON(msg proto.Message) []byte {
anyResolver := codectypes.NewInterfaceRegistry()

// EmitDefaults is set to false to prevent marshalling of unpopulated fields (memo)
// OrigName and the anyResovler match the fields the original SDK function would expect
// in order to minimize changes.

// OrigName is true since there is no particular reason to use camel case
// The any resolver is empty, but provided anyways.
jm := &jsonpb.Marshaler{OrigName: true, EmitDefaults: false, AnyResolver: anyResolver}

err := codectypes.UnpackInterfaces(msg, codectypes.ProtoJSONPacker{JSONPBMarshaler: jm})
if err != nil {
panic(err)
}

buf := new(bytes.Buffer)
if err := jm.Marshal(buf, msg); err != nil {
panic(err)
}

return buf.Bytes()
}
Loading

0 comments on commit b90f5c2

Please sign in to comment.