530 lines
15 KiB
Go
530 lines
15 KiB
Go
package testing
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"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"
|
|
channelexported "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/exported"
|
|
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 := GetChainID(i)
|
|
chains[chainID] = NewTestChain(t, chainID)
|
|
}
|
|
return &Coordinator{
|
|
t: t,
|
|
Chains: chains,
|
|
}
|
|
}
|
|
|
|
// Setup constructs a TM client, connection, and channel on both chains provided. It will
|
|
// fails if any error occurs. The clientID's, TestConnections, and TestChannels are returned
|
|
// for both chains.
|
|
func (coord *Coordinator) Setup(
|
|
chainA, chainB *TestChain,
|
|
) (string, string, *TestConnection, *TestConnection, TestChannel, TestChannel) {
|
|
clientA, clientB, connA, connB := coord.SetupClientConnections(chainA, chainB, clientexported.Tendermint)
|
|
|
|
// channels can also be referenced through the returned connections
|
|
channelA, channelB := coord.CreateChannel(chainA, chainB, connA, connB, channeltypes.UNORDERED)
|
|
|
|
return clientA, clientB, connA, connB, channelA, channelB
|
|
}
|
|
|
|
// SetupClients is a helper function to create clients on both chains. It assumes the
|
|
// caller does not anticipate any errors.
|
|
func (coord *Coordinator) SetupClients(
|
|
chainA, chainB *TestChain,
|
|
clientType clientexported.ClientType,
|
|
) (string, string) {
|
|
|
|
clientA, err := coord.CreateClient(chainA, chainB, clientType)
|
|
require.NoError(coord.t, err)
|
|
|
|
clientB, err := coord.CreateClient(chainB, chainA, clientType)
|
|
require.NoError(coord.t, err)
|
|
|
|
return clientA, clientB
|
|
}
|
|
|
|
// SetupClientConnections is a helper function to create clients and the appropriate
|
|
// connections on both the source and counterparty chain. It assumes the caller does not
|
|
// anticipate any errors.
|
|
func (coord *Coordinator) SetupClientConnections(
|
|
chainA, chainB *TestChain,
|
|
clientType clientexported.ClientType,
|
|
) (string, string, *TestConnection, *TestConnection) {
|
|
|
|
clientA, clientB := coord.SetupClients(chainA, chainB, clientType)
|
|
|
|
connA, connB := coord.CreateConnection(chainA, chainB, clientA, clientB)
|
|
|
|
return clientA, clientB, connA, connB
|
|
}
|
|
|
|
// CreateClient creates a counterparty client on the source chain and returns the clientID.
|
|
func (coord *Coordinator) CreateClient(
|
|
source, counterparty *TestChain,
|
|
clientType clientexported.ClientType,
|
|
) (clientID string, err error) {
|
|
coord.CommitBlock(source, counterparty)
|
|
|
|
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(
|
|
source, counterparty *TestChain,
|
|
clientID string,
|
|
clientType clientexported.ClientType,
|
|
) (err error) {
|
|
coord.CommitBlock(source, counterparty)
|
|
|
|
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 chainA and chainB. The connection information of for chainA and chainB
|
|
// are returned within a TestConnection struct. The function expects the connections to be
|
|
// successfully opened otherwise testing will fail.
|
|
func (coord *Coordinator) CreateConnection(
|
|
chainA, chainB *TestChain,
|
|
clientA, clientB string,
|
|
) (*TestConnection, *TestConnection) {
|
|
|
|
connA, connB, err := coord.ConnOpenInit(chainA, chainB, clientA, clientB)
|
|
require.NoError(coord.t, err)
|
|
|
|
err = coord.ConnOpenTry(chainB, chainA, connB, connA)
|
|
require.NoError(coord.t, err)
|
|
|
|
err = coord.ConnOpenAck(chainA, chainB, connA, connB)
|
|
require.NoError(coord.t, err)
|
|
|
|
err = coord.ConnOpenConfirm(chainB, chainA, connB, connA)
|
|
require.NoError(coord.t, err)
|
|
|
|
return connA, connB
|
|
}
|
|
|
|
// CreateChannel constructs and executes channel handshake messages in order to create
|
|
// OPEN channels on chainA and chainB. The function expects the channels to be successfully
|
|
// opened otherwise testing will fail.
|
|
func (coord *Coordinator) CreateChannel(
|
|
chainA, chainB *TestChain,
|
|
connA, connB *TestConnection,
|
|
order channeltypes.Order,
|
|
) (TestChannel, TestChannel) {
|
|
|
|
channelA, channelB, err := coord.ChanOpenInit(chainA, chainB, connA, connB, order)
|
|
require.NoError(coord.t, err)
|
|
|
|
err = coord.ChanOpenTry(chainB, chainA, channelB, channelA, connB, order)
|
|
require.NoError(coord.t, err)
|
|
|
|
err = coord.ChanOpenAck(chainA, chainB, channelA, channelB)
|
|
require.NoError(coord.t, err)
|
|
|
|
err = coord.ChanOpenConfirm(chainB, chainA, channelB, channelA)
|
|
require.NoError(coord.t, err)
|
|
|
|
return channelA, channelB
|
|
}
|
|
|
|
// SendPacket sends a packet through the channel keeper on the source chain and updates the
|
|
// counterparty client for the source chain.
|
|
func (coord *Coordinator) SendPacket(
|
|
source, counterparty *TestChain,
|
|
packet channelexported.PacketI,
|
|
counterpartyClientID string,
|
|
) error {
|
|
if err := source.SendPacket(packet); err != nil {
|
|
return err
|
|
}
|
|
coord.IncrementTime()
|
|
|
|
// update source client on counterparty connection
|
|
if err := coord.UpdateClient(
|
|
counterparty, source,
|
|
counterpartyClientID, clientexported.Tendermint,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// PacketExecuted receives a packet through the channel keeper on the source chain and updates the
|
|
// counterparty client for the source chain.
|
|
func (coord *Coordinator) PacketExecuted(
|
|
source, counterparty *TestChain,
|
|
packet channelexported.PacketI,
|
|
counterpartyClientID string,
|
|
) error {
|
|
if err := source.PacketExecuted(packet); err != nil {
|
|
return err
|
|
}
|
|
coord.IncrementTime()
|
|
|
|
// update source client on counterparty connection
|
|
if err := coord.UpdateClient(
|
|
counterparty, source,
|
|
counterpartyClientID, clientexported.Tendermint,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// 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.Time = chain.CurrentHeader.Time.Add(timeIncrement)
|
|
chain.App.BeginBlock(abci.RequestBeginBlock{Header: chain.CurrentHeader})
|
|
}
|
|
}
|
|
|
|
// GetChain returns the TestChain using the given chainID and returns an error if it does
|
|
// not exist.
|
|
func (coord *Coordinator) GetChain(chainID string) *TestChain {
|
|
chain, found := coord.Chains[chainID]
|
|
require.True(coord.t, found, fmt.Sprintf("%s chain does not exist", chainID))
|
|
return chain
|
|
}
|
|
|
|
// GetChainID returns the chainID used for the provided index.
|
|
func GetChainID(index int) string {
|
|
return ChainIDPrefix + strconv.Itoa(index)
|
|
}
|
|
|
|
// CommitBlock commits a block on the provided indexes and then increments the global time.
|
|
//
|
|
// CONTRACT: the passed in list of indexes must not contain duplicates
|
|
func (coord *Coordinator) CommitBlock(chains ...*TestChain) {
|
|
for _, chain := range chains {
|
|
chain.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(chain *TestChain, n uint64) {
|
|
for i := uint64(0); i < n; i++ {
|
|
chain.App.BeginBlock(abci.RequestBeginBlock{Header: chain.CurrentHeader})
|
|
chain.App.Commit()
|
|
chain.NextBlock()
|
|
coord.IncrementTime()
|
|
}
|
|
}
|
|
|
|
// ConnOpenInit initializes a connection on the source chain with the state INIT
|
|
// using the OpenInit handshake call.
|
|
//
|
|
// NOTE: The counterparty testing connection will be created even if it is not created in the
|
|
// application state.
|
|
func (coord *Coordinator) ConnOpenInit(
|
|
source, counterparty *TestChain,
|
|
clientID, counterpartyClientID string,
|
|
) (*TestConnection, *TestConnection, error) {
|
|
sourceConnection := source.NewTestConnection(clientID, counterpartyClientID)
|
|
counterpartyConnection := counterparty.NewTestConnection(counterpartyClientID, clientID)
|
|
|
|
// initialize connection on source
|
|
if err := source.ConnectionOpenInit(counterparty, sourceConnection, counterpartyConnection); err != nil {
|
|
return sourceConnection, counterpartyConnection, err
|
|
}
|
|
coord.IncrementTime()
|
|
|
|
// update source client on counterparty connection
|
|
if err := coord.UpdateClient(
|
|
counterparty, source,
|
|
counterpartyClientID, clientexported.Tendermint,
|
|
); err != nil {
|
|
return sourceConnection, counterpartyConnection, err
|
|
}
|
|
|
|
return sourceConnection, counterpartyConnection, nil
|
|
}
|
|
|
|
// ConnOpenTry initializes a connection on the source chain with the state TRYOPEN
|
|
// using the OpenTry handshake call.
|
|
func (coord *Coordinator) ConnOpenTry(
|
|
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, source,
|
|
counterpartyConnection.ClientID, clientexported.Tendermint,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ConnOpenAck initializes a connection on the source chain with the state OPEN
|
|
// using the OpenAck handshake call.
|
|
func (coord *Coordinator) ConnOpenAck(
|
|
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, source,
|
|
counterpartyConnection.ClientID, clientexported.Tendermint,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ConnOpenConfirm initializes a connection on the source chain with the state OPEN
|
|
// using the OpenConfirm handshake call.
|
|
func (coord *Coordinator) ConnOpenConfirm(
|
|
source, counterparty *TestChain,
|
|
sourceConnection, counterpartyConnection *TestConnection,
|
|
) error {
|
|
if err := source.ConnectionOpenConfirm(counterparty, sourceConnection, counterpartyConnection); err != nil {
|
|
return err
|
|
}
|
|
coord.IncrementTime()
|
|
|
|
// update source client on counterparty connection
|
|
if err := coord.UpdateClient(
|
|
counterparty, source,
|
|
counterpartyConnection.ClientID, clientexported.Tendermint,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ChanOpenInit initializes a channel on the source chain with the state INIT
|
|
// using the OpenInit handshake call.
|
|
//
|
|
// NOTE: The counterparty testing channel will be created even if it is not created in the
|
|
// application state.
|
|
func (coord *Coordinator) ChanOpenInit(
|
|
source, counterparty *TestChain,
|
|
connection, counterpartyConnection *TestConnection,
|
|
order channeltypes.Order,
|
|
) (TestChannel, TestChannel, error) {
|
|
sourceChannel := connection.AddTestChannel()
|
|
counterpartyChannel := counterpartyConnection.AddTestChannel()
|
|
|
|
// create port capability
|
|
source.CreatePortCapability(sourceChannel.PortID)
|
|
coord.IncrementTime()
|
|
|
|
// initialize channel on source
|
|
if err := source.ChanOpenInit(sourceChannel, counterpartyChannel, order, connection.ID); err != nil {
|
|
return sourceChannel, counterpartyChannel, err
|
|
}
|
|
coord.IncrementTime()
|
|
|
|
// update source client on counterparty connection
|
|
if err := coord.UpdateClient(
|
|
counterparty, source,
|
|
counterpartyConnection.ClientID, clientexported.Tendermint,
|
|
); err != nil {
|
|
return sourceChannel, counterpartyChannel, err
|
|
}
|
|
|
|
return sourceChannel, counterpartyChannel, nil
|
|
}
|
|
|
|
// ChanOpenTry initializes a channel on the source chain with the state TRYOPEN
|
|
// using the OpenTry handshake call.
|
|
func (coord *Coordinator) ChanOpenTry(
|
|
source, counterparty *TestChain,
|
|
sourceChannel, counterpartyChannel TestChannel,
|
|
connection *TestConnection,
|
|
order channeltypes.Order,
|
|
) error {
|
|
|
|
// initialize channel on source
|
|
if err := source.ChanOpenTry(counterparty, sourceChannel, counterpartyChannel, order, connection.ID); err != nil {
|
|
return err
|
|
}
|
|
coord.IncrementTime()
|
|
|
|
// update source client on counterparty connection
|
|
if err := coord.UpdateClient(
|
|
counterparty, source,
|
|
connection.CounterpartyClientID, clientexported.Tendermint,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ChanOpenAck initializes a channel on the source chain with the state OPEN
|
|
// using the OpenAck handshake call.
|
|
func (coord *Coordinator) ChanOpenAck(
|
|
source, counterparty *TestChain,
|
|
sourceChannel, counterpartyChannel TestChannel,
|
|
) error {
|
|
|
|
if err := source.ChanOpenAck(counterparty, sourceChannel, counterpartyChannel); err != nil {
|
|
return err
|
|
}
|
|
coord.IncrementTime()
|
|
|
|
// update source client on counterparty connection
|
|
if err := coord.UpdateClient(
|
|
counterparty, source,
|
|
sourceChannel.CounterpartyClientID, clientexported.Tendermint,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ChanOpenConfirm initializes a channel on the source chain with the state OPEN
|
|
// using the OpenConfirm handshake call.
|
|
func (coord *Coordinator) ChanOpenConfirm(
|
|
source, counterparty *TestChain,
|
|
sourceChannel, counterpartyChannel TestChannel,
|
|
) error {
|
|
|
|
if err := source.ChanOpenConfirm(counterparty, sourceChannel, counterpartyChannel); err != nil {
|
|
return err
|
|
}
|
|
coord.IncrementTime()
|
|
|
|
// update source client on counterparty connection
|
|
if err := coord.UpdateClient(
|
|
counterparty, source,
|
|
sourceChannel.CounterpartyClientID, clientexported.Tendermint,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ChanCloseInit closes a channel on the source chain resulting in the channels state
|
|
// being set to CLOSED.
|
|
//
|
|
// NOTE: does not work with ibc-transfer module
|
|
func (coord *Coordinator) ChanCloseInit(
|
|
source, counterparty *TestChain,
|
|
channel TestChannel,
|
|
) error {
|
|
|
|
if err := source.ChanCloseInit(counterparty, channel); err != nil {
|
|
return err
|
|
}
|
|
coord.IncrementTime()
|
|
|
|
// update source client on counterparty connection
|
|
if err := coord.UpdateClient(
|
|
counterparty, source,
|
|
channel.CounterpartyClientID, clientexported.Tendermint,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetChannelClosed sets a channel state to CLOSED.
|
|
func (coord *Coordinator) SetChannelClosed(
|
|
source, counterparty *TestChain,
|
|
testChannel TestChannel,
|
|
) error {
|
|
channel := source.GetChannel(testChannel)
|
|
|
|
channel.State = channeltypes.CLOSED
|
|
source.App.IBCKeeper.ChannelKeeper.SetChannel(source.GetContext(), testChannel.PortID, testChannel.ID, channel)
|
|
|
|
coord.CommitBlock(source)
|
|
|
|
// update source client on counterparty connection
|
|
if err := coord.UpdateClient(
|
|
counterparty, source,
|
|
testChannel.CounterpartyClientID, clientexported.Tendermint,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|