409 lines
13 KiB
Go
409 lines
13 KiB
Go
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 proto encoded merkle proof
|
|
// for the query and the height at which the query was performed.
|
|
func (chain *TestChain) QueryProof(key []byte) ([]byte, uint64) {
|
|
res := chain.App.Query(abci.RequestQuery{
|
|
Path: fmt.Sprintf("store/%s/key", host.StoreKey),
|
|
Height: chain.App.LastBlockHeight(),
|
|
Data: key,
|
|
Prove: true,
|
|
})
|
|
|
|
merkleProof := commitmenttypes.MerkleProof{
|
|
Proof: res.Proof,
|
|
}
|
|
|
|
proof, err := chain.App.AppCodec().MarshalBinaryBare(&merkleProof)
|
|
require.NoError(chain.t, err)
|
|
|
|
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<counterparty-chain-id><index>
|
|
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<index>
|
|
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<index>
|
|
// portid<index>
|
|
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,
|
|
commitmenttypes.GetSDKSpecs(), 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)
|
|
}
|