diff --git a/x/ibc/testing/chain.go b/x/ibc/testing/chain.go new file mode 100644 index 0000000000..8a46f9c986 --- /dev/null +++ b/x/ibc/testing/chain.go @@ -0,0 +1,405 @@ +package testing + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/secp256k1" + tmmath "github.com/tendermint/tendermint/libs/math" + lite "github.com/tendermint/tendermint/lite2" + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/cosmos/cosmos-sdk/simapp" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + connectiontypes "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/types" + channeltypes "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types" + ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types" + commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types" + host "github.com/cosmos/cosmos-sdk/x/ibc/24-host" + "github.com/cosmos/cosmos-sdk/x/ibc/keeper" +) + +const ( + // Default params used to create a TM client + TrustingPeriod time.Duration = time.Hour * 24 * 7 * 2 + UnbondingPeriod time.Duration = time.Hour * 24 * 7 * 3 + MaxClockDrift time.Duration = time.Second * 10 + + ConnectionVersion = "1.0" + ChannelVersion = "1.0" + + ClientIDPrefix = "clientFor" + ConnectionIDPrefix = "connectionid" + ChannelIDPrefix = "channelid" + PortIDPrefix = "portid" +) + +var ( + DefaultTrustLevel tmmath.Fraction = lite.DefaultTrustLevel +) + +// TestChain is a testing struct that wraps a simapp with the last TM Header, the current ABCI +// header and the validators of the TestChain. It also contains a field called ChainID. This +// is the clientID that *other* chains use to refer to this TestChain. The SenderAccount +// is used for delivering transactions through the application state. +// NOTE: the actual application uses an empty chain-id for ease of testing. +type TestChain struct { + t *testing.T + + App *simapp.SimApp + ChainID string + LastHeader ibctmtypes.Header // header for last block height committed + CurrentHeader abci.Header // header for current block height + Querier sdk.Querier + + Vals *tmtypes.ValidatorSet + Signers []tmtypes.PrivValidator + + senderPrivKey crypto.PrivKey + SenderAccount authtypes.AccountI + + // IBC specific helpers + ClientIDs []string // ClientID's used on this chain + Connections []TestConnection // track connectionID's created for this chain + Channels []TestChannel // track portID/channelID's created for this chain +} + +// NewTestChain initializes a new TestChain instance with a single validator set using a +// generated private key. It also creates a sender account to be used for delivering transactions. +// +// The first block height is committed to state in order to allow for client creations on +// counterparty chains. The TestChain will return with a block height starting at 2. +// +// Time management is handled by the Coordinator in order to ensure synchrony between chains. +// Each update of any chain increments the block header time for all chains by 5 seconds. +func NewTestChain(t *testing.T, chainID string) *TestChain { + // generate validator private/public key + privVal := tmtypes.NewMockPV() + pubKey, err := privVal.GetPubKey() + require.NoError(t, err) + + // create validator set with single validator + validator := tmtypes.NewValidator(pubKey, 1) + valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{validator}) + signers := []tmtypes.PrivValidator{privVal} + + app := simapp.Setup(false) + ctx := app.BaseApp.NewContext(false, + abci.Header{ + Height: 1, + Time: globalStartTime, + }, + ) + + // generate and set SenderAccount + senderPrivKey := secp256k1.GenPrivKey() + simapp.AddTestAddrsFromPubKeys(app, ctx, []crypto.PubKey{senderPrivKey.PubKey()}, sdk.NewInt(10000000000)) + acc := app.AccountKeeper.GetAccount(ctx, sdk.AccAddress(senderPrivKey.PubKey().Address())) + + // commit init chain changes so create client can be called by a counterparty chain + app.Commit() + // create current header and call begin block + header := abci.Header{ + Height: 2, + Time: globalStartTime.Add(timeIncrement), + } + app.BeginBlock(abci.RequestBeginBlock{Header: header}) + + lastHeader := ibctmtypes.CreateTestHeader(chainID, 1, globalStartTime, valSet, signers) + + // create an account to send transactions from + return &TestChain{ + t: t, + ChainID: chainID, + App: app, + LastHeader: lastHeader, + CurrentHeader: header, + Querier: keeper.NewQuerier(*app.IBCKeeper), + Vals: valSet, + Signers: signers, + senderPrivKey: senderPrivKey, + SenderAccount: acc, + } +} + +// GetContext returns the current context for the application. +func (chain *TestChain) GetContext() sdk.Context { + return chain.App.BaseApp.NewContext(false, chain.CurrentHeader) +} + +// QueryProof performs an abci query with the given key and returns the merkle proof for the query +// and the height at which the query was performed. +func (chain *TestChain) QueryProof(key []byte) (commitmenttypes.MerkleProof, uint64) { + res := chain.App.Query(abci.RequestQuery{ + Path: fmt.Sprintf("store/%s/key", host.StoreKey), + Height: chain.App.LastBlockHeight(), + Data: key, + Prove: true, + }) + + proof := commitmenttypes.MerkleProof{ + Proof: res.Proof, + } + + return proof, uint64(res.Height) +} + +// NextBlock sets the last header to the current header and increments the current header to be +// at the next block height. It does not update the time as that is handled by the Coordinator. +// +// CONTRACT: this function must only be called after app.Commit() occurs +func (chain *TestChain) NextBlock() { + // set the last header to the current header + chain.LastHeader = ibctmtypes.CreateTestHeader( + chain.CurrentHeader.ChainID, + chain.CurrentHeader.Height, + chain.CurrentHeader.Time, + chain.Vals, chain.Signers, + ) + + // increment the current header + chain.CurrentHeader = abci.Header{ + Height: chain.CurrentHeader.Height + 1, + Time: chain.CurrentHeader.Time, + } +} + +// SendMsg delivers a transaction through the application. It updates the senders sequence +// number and updates the TestChain's headers. +func (chain *TestChain) SendMsg(msg sdk.Msg) error { + _, _, err := simapp.SignCheckDeliver( + chain.t, + chain.App.Codec(), + chain.App.BaseApp, + chain.GetContext().BlockHeader(), + []sdk.Msg{msg}, + []uint64{chain.SenderAccount.GetAccountNumber()}, + []uint64{chain.SenderAccount.GetSequence()}, + true, true, chain.senderPrivKey, + ) + if err != nil { + return err + } + + // SignCheckDeliver calls app.Commit() + chain.NextBlock() + + // increment sequence for successful transaction execution + chain.SenderAccount.SetSequence(chain.SenderAccount.GetSequence() + 1) + + return nil +} + +// NewClientID appends a new clientID string in the format: +// ClientFor +func (chain *TestChain) NewClientID(counterpartyChainID string) string { + clientID := ClientIDPrefix + counterpartyChainID + string(len(chain.ClientIDs)) + + chain.ClientIDs = append(chain.ClientIDs, clientID) + return clientID +} + +// NewConnection appends a new TestConnection which contains references to the connection id, +// client id and counterparty client id. The connection id format: +// connectionid +func (chain *TestChain) NewTestConnection(clientID, counterpartyClientID string) TestConnection { + connectionID := ConnectionIDPrefix + string(len(chain.Connections)) + conn := TestConnection{ + ID: connectionID, + ClientID: clientID, + CounterpartyClientID: counterpartyClientID, + } + + chain.Connections = append(chain.Connections, conn) + return conn +} + +// NewTestChannel appends a new TestChannel which contains references to the port and channel ID +// used for channel creation and interaction. The channel id and port id format: +// channelid +// portid +func (chain *TestChain) NewTestChannel() TestChannel { + portID := PortIDPrefix + string(len(chain.Channels)) + channelID := ChannelIDPrefix + string(len(chain.Channels)) + channel := TestChannel{ + PortID: portID, + ChannelID: channelID, + } + + chain.Channels = append(chain.Channels, channel) + + return channel +} + +// CreateTMClient will construct and execute a 07-tendermint MsgCreateClient. A counterparty +// client will be created on the (target) chain. +func (chain *TestChain) CreateTMClient(counterparty *TestChain, clientID string) error { + // construct MsgCreateClient using counterparty + msg := ibctmtypes.NewMsgCreateClient( + clientID, counterparty.LastHeader, + DefaultTrustLevel, TrustingPeriod, UnbondingPeriod, MaxClockDrift, + chain.SenderAccount.GetAddress(), + ) + + return chain.SendMsg(msg) +} + +// UpdateTMClient will construct and execute a 07-tendermint MsgUpdateClient. The counterparty +// client will be updated on the (target) chain. +func (chain *TestChain) UpdateTMClient(counterparty *TestChain, clientID string) error { + msg := ibctmtypes.NewMsgUpdateClient( + clientID, counterparty.LastHeader, + chain.SenderAccount.GetAddress(), + ) + + return chain.SendMsg(msg) +} + +// ConnectionOpenInit will construct and execute a MsgConnectionOpenInit. +func (chain *TestChain) ConnectionOpenInit( + counterparty *TestChain, + connection, counterpartyConnection TestConnection, +) error { + prefix := commitmenttypes.NewMerklePrefix(counterparty.App.IBCKeeper.ConnectionKeeper.GetCommitmentPrefix().Bytes()) + + msg := connectiontypes.NewMsgConnectionOpenInit( + connection.ID, connection.ClientID, + counterpartyConnection.ID, connection.CounterpartyClientID, + prefix, + chain.SenderAccount.GetAddress(), + ) + return chain.SendMsg(msg) +} + +// ConnectionOpenTry will construct and execute a MsgConnectionOpenTry. +func (chain *TestChain) ConnectionOpenTry( + counterparty *TestChain, + connection, counterpartyConnection TestConnection, +) error { + prefix := commitmenttypes.NewMerklePrefix(counterparty.App.IBCKeeper.ConnectionKeeper.GetCommitmentPrefix().Bytes()) + + connectionKey := host.KeyConnection(counterpartyConnection.ID) + proofInit, proofHeight := counterparty.QueryProof(connectionKey) + + consensusHeight := uint64(counterparty.App.LastBlockHeight()) + consensusKey := prefixedClientKey(connection.ClientID, host.KeyConsensusState(consensusHeight)) + proofConsensus, _ := counterparty.QueryProof(consensusKey) + + msg := connectiontypes.NewMsgConnectionOpenTry( + connection.ID, connection.ClientID, + counterpartyConnection.ID, connection.CounterpartyClientID, + prefix, []string{ConnectionVersion}, + proofInit, proofConsensus, + proofHeight, consensusHeight, + chain.SenderAccount.GetAddress(), + ) + return chain.SendMsg(msg) +} + +// ConnectionOpenAck will construct and execute a MsgConnectionOpenAck. +func (chain *TestChain) ConnectionOpenAck( + counterparty *TestChain, + connection, counterpartyConnection TestConnection, +) error { + connectionKey := host.KeyConnection(counterpartyConnection.ID) + proofTry, proofHeight := counterparty.QueryProof(connectionKey) + + consensusHeight := uint64(counterparty.App.LastBlockHeight()) + consensusKey := prefixedClientKey(connection.ClientID, host.KeyConsensusState(consensusHeight)) + proofConsensus, _ := counterparty.QueryProof(consensusKey) + + msg := connectiontypes.NewMsgConnectionOpenAck( + connection.ID, + proofTry, proofConsensus, + proofHeight, consensusHeight, + ConnectionVersion, + chain.SenderAccount.GetAddress(), + ) + return chain.SendMsg(msg) +} + +// ConnectionOpenConfirm will construct and execute a MsgConnectionOpenConfirm. +func (chain *TestChain) ConnectionOpenConfirm( + counterparty *TestChain, + connection, counterpartyConnection TestConnection, +) error { + connectionKey := host.KeyConnection(counterpartyConnection.ID) + proof, height := counterparty.QueryProof(connectionKey) + + msg := connectiontypes.NewMsgConnectionOpenConfirm( + connection.ID, + proof, height, + chain.SenderAccount.GetAddress(), + ) + return chain.SendMsg(msg) +} + +// ChannelOpenInit will construct and execute a MsgChannelOpenInit. +func (chain *TestChain) ChannelOpenInit( + ch, counterparty TestChannel, + order channeltypes.Order, + connectionID string, +) error { + msg := channeltypes.NewMsgChannelOpenInit( + ch.PortID, ch.ChannelID, + ChannelVersion, order, []string{connectionID}, + counterparty.PortID, counterparty.ChannelID, + chain.SenderAccount.GetAddress(), + ) + return chain.SendMsg(msg) +} + +// ChannelOpenTry will construct and execute a MsgChannelOpenTry. +func (chain *TestChain) ChannelOpenTry( + ch, counterparty TestChannel, + order channeltypes.Order, + connectionID string, +) error { + proof, height := chain.QueryProof(host.KeyConnection(connectionID)) + + msg := channeltypes.NewMsgChannelOpenTry( + ch.PortID, ch.ChannelID, + ChannelVersion, order, []string{connectionID}, + counterparty.PortID, counterparty.ChannelID, + ChannelVersion, + proof, height, + chain.SenderAccount.GetAddress(), + ) + return chain.SendMsg(msg) +} + +// ChannelOpenAck will construct and execute a MsgChannelOpenAck. +func (chain *TestChain) ChannelOpenAck( + ch, counterparty TestChannel, + connectionID string, +) error { + proof, height := chain.QueryProof(host.KeyConnection(connectionID)) + + msg := channeltypes.NewMsgChannelOpenAck( + ch.PortID, ch.ChannelID, + ChannelVersion, + proof, height, + chain.SenderAccount.GetAddress(), + ) + return chain.SendMsg(msg) +} + +// ChannelOpenConfirm will construct and execute a MsgChannelOpenConfirm. +func (chain *TestChain) ChannelOpenConfirm( + ch, counterparty TestChannel, + connectionID string, +) error { + proof, height := chain.QueryProof(host.KeyConnection(connectionID)) + + msg := channeltypes.NewMsgChannelOpenConfirm( + ch.PortID, ch.ChannelID, + proof, height, + chain.SenderAccount.GetAddress(), + ) + return chain.SendMsg(msg) +} diff --git a/x/ibc/testing/coordinator.go b/x/ibc/testing/coordinator.go new file mode 100644 index 0000000000..51040b3d8a --- /dev/null +++ b/x/ibc/testing/coordinator.go @@ -0,0 +1,411 @@ +package testing + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + + clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" + connectiontypes "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/types" + channeltypes "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types" +) + +var ( + ChainIDPrefix = "testchain" + globalStartTime = time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC) + timeIncrement = time.Second * 5 +) + +// Coordinator is a testing struct which contains N TestChain's. It handles keeping all chains +// in sync with regards to time. +type Coordinator struct { + t *testing.T + + Chains map[string]*TestChain +} + +// NewCoordinator initializes Coordinator with N TestChain's +func NewCoordinator(t *testing.T, n int) *Coordinator { + chains := make(map[string]*TestChain) + + for i := 0; i < n; i++ { + chainID := ChainIDPrefix + string(i) + chains[chainID] = NewTestChain(t, chainID) + } + return &Coordinator{ + t: t, + Chains: chains, + } +} + +// IncrementTime iterates through all the TestChain's and increments their current header time +// by 5 seconds. +// +// CONTRACT: this function must be called after every commit on any TestChain. +func (coord *Coordinator) IncrementTime() { + for _, chain := range coord.Chains { + chain.CurrentHeader = abci.Header{ + Height: chain.CurrentHeader.Height, + Time: chain.CurrentHeader.Time.Add((timeIncrement)), + } + } +} + +// GetChain returns the TestChain using the given chainID and returns an error if it does +// not exist. +func (coord *Coordinator) GetChain(chainID string) *TestChain { + chain, found := coord.Chains[chainID] + require.True(coord.t, found, fmt.Sprintf("%s chain does not exist", chainID)) + return chain +} + +// CommitBlock commits a block on the provided indexes and then increments the global time. +// +// CONTRACT: the passed in list of indexes must not contain duplicates +func (coord *Coordinator) CommitBlock(chains ...string) { + for _, chainID := range chains { + chain := coord.GetChain(chainID) + chain.App.Commit() + chain.NextBlock() + } + coord.IncrementTime() +} + +// CommitNBlocks commits n blocks to state and updates the block height by 1 for each commit. +func (coord *Coordinator) CommitNBlocks(chainID string, n uint64) { + chain := coord.GetChain(chainID) + + for i := uint64(0); i < n; i++ { + chain.App.BeginBlock(abci.RequestBeginBlock{Header: chain.CurrentHeader}) + chain.App.Commit() + chain.NextBlock() + coord.IncrementTime() + } +} + +// CreateClient creates a counterparty client on the source chain and returns the clientID. +func (coord *Coordinator) CreateClient( + sourceID, counterpartyID string, + clientType clientexported.ClientType, +) (clientID string, err error) { + coord.CommitBlock(sourceID, counterpartyID) + + source := coord.GetChain(sourceID) + counterparty := coord.GetChain(counterpartyID) + + clientID = source.NewClientID(counterparty.ChainID) + + switch clientType { + case clientexported.Tendermint: + err = source.CreateTMClient(counterparty, clientID) + + default: + err = fmt.Errorf("client type %s is not supported", clientType) + } + + if err != nil { + return "", err + } + + coord.IncrementTime() + + return clientID, nil +} + +// UpdateClient updates a counterparty client on the source chain. +func (coord *Coordinator) UpdateClient( + sourceID, counterpartyID, + clientID string, + clientType clientexported.ClientType, +) (err error) { + coord.CommitBlock(sourceID, counterpartyID) + + source := coord.GetChain(sourceID) + counterparty := coord.GetChain(counterpartyID) + + switch clientType { + case clientexported.Tendermint: + err = source.UpdateTMClient(counterparty, clientID) + + default: + err = fmt.Errorf("client type %s is not supported", clientType) + } + + if err != nil { + return err + } + + coord.IncrementTime() + + return nil +} + +// CreateConnection constructs and executes connection handshake messages in order to create +// OPEN channels on source and counterparty chains. The connection information of the source +// and counterparty's are returned within a TestConnection struct. If there is a fault in +// the connection handshake then an error is returned. +// +// NOTE: The counterparty testing connection will be created even if it is not created in the +// application state. +func (coord *Coordinator) CreateConnection( + sourceID, counterpartyID, + clientID, counterpartyClientID string, + state connectiontypes.State, +) (TestConnection, TestConnection, error) { + source := coord.GetChain(sourceID) + counterparty := coord.GetChain(counterpartyID) + + sourceConnection := source.NewTestConnection(clientID, counterpartyClientID) + counterpartyConnection := counterparty.NewTestConnection(counterpartyClientID, clientID) + + if err := coord.CreateConnectionInit(source, counterparty, sourceConnection, counterpartyConnection); err != nil { + return sourceConnection, counterpartyConnection, err + } + + if err := coord.CreateConnectionOpenTry(counterparty, source, counterpartyConnection, sourceConnection); err != nil { + return sourceConnection, counterpartyConnection, err + } + + if err := coord.CreateConnectionOpenAck(source, counterparty, sourceConnection, counterpartyConnection); err != nil { + return sourceConnection, counterpartyConnection, err + } + + if err := coord.CreateConnectionOpenConfirm(counterparty, source, counterpartyConnection, sourceConnection); err != nil { + return sourceConnection, counterpartyConnection, err + } + + return sourceConnection, counterpartyConnection, nil +} + +// CreateConenctionInit initializes a connection on the source chain with the state INIT +// using the OpenInit handshake call. +func (coord *Coordinator) CreateConnectionInit( + source, counterparty *TestChain, + sourceConnection, counterpartyConnection TestConnection, +) error { + // initialize connection on source + if err := source.ConnectionOpenInit(counterparty, sourceConnection, counterpartyConnection); err != nil { + return err + } + coord.IncrementTime() + + // update source client on counterparty connection + if err := coord.UpdateClient( + counterparty.ChainID, source.ChainID, + counterpartyConnection.ClientID, clientexported.Tendermint, + ); err != nil { + return err + } + + return nil +} + +// CreateConenctionOpenTry initializes a connection on the source chain with the state TRYOPEN +// using the OpenTry handshake call. +func (coord *Coordinator) CreateConnectionOpenTry( + source, counterparty *TestChain, + sourceConnection, counterpartyConnection TestConnection, +) error { + // initialize TRYOPEN connection on source + if err := source.ConnectionOpenTry(counterparty, sourceConnection, counterpartyConnection); err != nil { + return err + } + coord.IncrementTime() + + // update source client on counterparty connection + if err := coord.UpdateClient( + counterparty.ChainID, source.ChainID, + counterpartyConnection.ClientID, clientexported.Tendermint, + ); err != nil { + return err + } + + return nil +} + +// CreateConnectionOpenAck initializes a connection on the source chain with the state OPEN +// using the OpenAck handshake call. +func (coord *Coordinator) CreateConnectionOpenAck( + source, counterparty *TestChain, + sourceConnection, counterpartyConnection TestConnection, +) error { + // set OPEN connection on source using OpenAck + if err := source.ConnectionOpenAck(counterparty, sourceConnection, counterpartyConnection); err != nil { + return err + } + coord.IncrementTime() + + // update source client on counterparty connection + if err := coord.UpdateClient( + counterparty.ChainID, source.ChainID, + counterpartyConnection.ClientID, clientexported.Tendermint, + ); err != nil { + return err + } + + return nil +} + +// CreateConnectionOpenConfirm initializes a connection on the source chain with the state OPEN +// using the OpenConfirm handshake call. +func (coord *Coordinator) CreateConnectionOpenConfirm( + source, counterparty *TestChain, + sourceConnection, counterpartyConnection TestConnection, +) error { + if err := counterparty.ConnectionOpenConfirm(counterparty, sourceConnection, counterpartyConnection); err != nil { + return err + } + coord.IncrementTime() + + // update source client on counterparty connection + if err := coord.UpdateClient( + source.ChainID, counterparty.ChainID, + sourceConnection.ClientID, clientexported.Tendermint, + ); err != nil { + return err + } + + return nil +} + +// CreateChannel constructs and executes channel handshake messages in order to create +// channels on source and counterparty chains with the passed in Channel State. The portID and +// channelID of source and counterparty are returned. +// +// NOTE: The counterparty testing channel will be created even if it is not created in the +// application state. +func (coord *Coordinator) CreateChannel( + sourceID, counterpartyID string, + connection, counterpartyConnection TestConnection, + order channeltypes.Order, + state channeltypes.State, +) (TestChannel, TestChannel, error) { + source := coord.GetChain(sourceID) + counterparty := coord.GetChain(counterpartyID) + + sourceChannel := source.NewTestChannel() + counterpartyChannel := counterparty.NewTestChannel() + + if err := coord.CreateChannelInit(source, counterparty, sourceChannel, counterpartyChannel, connection, order); err != nil { + return sourceChannel, counterpartyChannel, err + } + + if err := coord.CreateChannelOpenTry(counterparty, source, counterpartyChannel, sourceChannel, counterpartyConnection, order); err != nil { + return sourceChannel, counterpartyChannel, err + } + + if err := coord.CreateChannelOpenAck(source, counterparty, sourceChannel, counterpartyChannel, connection); err != nil { + return sourceChannel, counterpartyChannel, err + } + + if err := coord.CreateChannelOpenConfirm(counterparty, source, counterpartyChannel, sourceChannel, counterpartyConnection); err != nil { + return sourceChannel, counterpartyChannel, err + } + + return sourceChannel, counterpartyChannel, nil +} + +// CreateChannelInit initializes a channel on the source chain with the state INIT +// using the OpenInit handshake call. +func (coord *Coordinator) CreateChannelInit( + source, counterparty *TestChain, + sourceChannel, counterpartyChannel TestChannel, + connection TestConnection, + order channeltypes.Order, +) error { + + // initialize channel on source + if err := source.ChannelOpenInit(sourceChannel, counterpartyChannel, order, connection.ID); err != nil { + return err + } + coord.IncrementTime() + + // update source client on counterparty connection + if err := coord.UpdateClient( + counterparty.ChainID, source.ChainID, + connection.CounterpartyClientID, clientexported.Tendermint, + ); err != nil { + return err + } + + return nil +} + +// CreateChannelOpenTry initializes a channel on the source chain with the state TRYOPEN +// using the OpenTry handshake call. +func (coord *Coordinator) CreateChannelOpenTry( + source, counterparty *TestChain, + sourceChannel, counterpartyChannel TestChannel, + connection TestConnection, + order channeltypes.Order, +) error { + + // initialize channel on source + if err := source.ChannelOpenTry(sourceChannel, counterpartyChannel, order, connection.ID); err != nil { + return err + } + coord.IncrementTime() + + // update source client on counterparty connection + if err := coord.UpdateClient( + counterparty.ChainID, source.ChainID, + connection.CounterpartyClientID, clientexported.Tendermint, + ); err != nil { + return err + } + + return nil +} + +// CreateChannelOpenAck initializes a channel on the source chain with the state OPEN +// using the OpenAck handshake call. +func (coord *Coordinator) CreateChannelOpenAck( + source, counterparty *TestChain, + sourceChannel, counterpartyChannel TestChannel, + connection TestConnection, +) error { + + // initialize channel on source + if err := source.ChannelOpenAck(sourceChannel, counterpartyChannel, connection.ID); err != nil { + return err + } + coord.IncrementTime() + + // update source client on counterparty connection + if err := coord.UpdateClient( + counterparty.ChainID, source.ChainID, + connection.CounterpartyClientID, clientexported.Tendermint, + ); err != nil { + return err + } + + return nil +} + +// CreateChannelOpenConfirm initializes a channel on the source chain with the state OPEN +// using the OpenConfirm handshake call. +func (coord *Coordinator) CreateChannelOpenConfirm( + source, counterparty *TestChain, + sourceChannel, counterpartyChannel TestChannel, + connection TestConnection, +) error { + + // initialize channel on source + if err := source.ChannelOpenConfirm(sourceChannel, counterpartyChannel, connection.ID); err != nil { + return err + } + coord.IncrementTime() + + // update source client on counterparty connection + if err := coord.UpdateClient( + counterparty.ChainID, source.ChainID, + connection.CounterpartyClientID, clientexported.Tendermint, + ); err != nil { + return err + } + + return nil +} diff --git a/x/ibc/testing/keys.go b/x/ibc/testing/keys.go new file mode 100644 index 0000000000..5d307f6a6a --- /dev/null +++ b/x/ibc/testing/keys.go @@ -0,0 +1,5 @@ +package testing + +func prefixedClientKey(clientID string, key []byte) []byte { + return append([]byte("clients/"+clientID+"/"), key...) +} diff --git a/x/ibc/testing/types.go b/x/ibc/testing/types.go new file mode 100644 index 0000000000..672a484c52 --- /dev/null +++ b/x/ibc/testing/types.go @@ -0,0 +1,16 @@ +package testing + +// TestConnections is a testing helper struct to keep track of the connectionID, source clientID, +// and counterparty clientID used in creating and interacting with a connection. +type TestConnection struct { + ID string + ClientID string + CounterpartyClientID string +} + +// TestChannel is a testing helper struct to keep track of the portID and channelID +// used in creating and interacting with a channel. +type TestChannel struct { + PortID string + ChannelID string +}