diff --git a/x/ibc-dns/server/keeper/keeper_test.go b/x/ibc-dns/server/keeper/keeper_test.go deleted file mode 100644 index 944f446..0000000 --- a/x/ibc-dns/server/keeper/keeper_test.go +++ /dev/null @@ -1,283 +0,0 @@ -package keeper_test - -import ( - "fmt" - "os" - "testing" - "time" - - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - connectiontypes "github.com/cosmos/cosmos-sdk/x/ibc/core/03-connection/types" - channeltypes "github.com/cosmos/cosmos-sdk/x/ibc/core/04-channel/types" - commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/core/23-commitment/types" - ibctypes "github.com/cosmos/cosmos-sdk/x/ibc/core/types" - "github.com/stretchr/testify/suite" - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/libs/log" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - tmtypes "github.com/tendermint/tendermint/types" - - "github.com/datachainlab/cosmos-sdk-interchain-dns/example/simapp" -) - -// define constants used for testing -const ( - testChannelOrder = channeltypes.UNORDERED - testChannelVersion = "1.0" -) - -const ( - trustingPeriod time.Duration = time.Hour * 24 * 7 * 2 - ubdPeriod time.Duration = time.Hour * 24 * 7 * 3 - maxClockDrift time.Duration = time.Second * 10 -) - -type KeeperTestSuite struct { - suite.Suite -} - -func (suite *KeeperTestSuite) SetupSuite() {} - -type ChannelInfo struct { - Port string `json:"port" yaml:"port"` // the port on which the packet will be sent - Channel string `json:"channel" yaml:"channel"` // the channel by which the packet will be sent -} - -type appContext struct { - chainID string - cdc *codec.LegacyAmino - ctx sdk.Context - app *simapp.SimApp - valSet *tmtypes.ValidatorSet - signers []tmtypes.PrivValidator - - // src => dst - channels map[ChannelInfo]ChannelInfo -} - -func (a appContext) Cache() (appContext, func()) { - ctx, writer := a.ctx.CacheContext() - a.ctx = ctx - return a, writer -} - -func (suite *KeeperTestSuite) createClient(actx *appContext, clientID string, skipIfClientExists bool) { - actx.app.Commit() - - h := tmtypes.Header{ChainID: actx.ctx.ChainID(), Height: actx.app.LastBlockHeight() + 1} - actx.app.BeginBlock(abci.RequestBeginBlock{Header: h}) - actx.ctx = actx.ctx.WithBlockHeader(h) - now := time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC) - - header := tendermint.CreateTestHeader(actx.chainID, 1, now, actx.valSet, actx.signers) - consensusState := header.ConsensusState() - - clientState, err := tendermint.Initialize(clientID, trustingPeriod, ubdPeriod, maxClockDrift, header) - if err != nil { - panic(err) - } - - if skipIfClientExists { - _, found := actx.app.IBCKeeper.ClientKeeper.GetClientState(actx.ctx, clientID) - if found { - return - } - } - - err = actx.app.IBCKeeper.ClientKeeper.CreateClient(actx.ctx, clientID, clientState, consensusState) - suite.NoError(err) -} - -func (suite *KeeperTestSuite) updateClient(actx *appContext, clientID string) { - // always commit and begin a new block on updateClient - actx.app.Commit() - commitID := actx.app.LastCommitID() - - h := tmproto.Header{ChainID: actx.ctx.ChainID(), Height: actx.app.LastBlockHeight() + 1} - actx.app.BeginBlock(abci.RequestBeginBlock{Header: h}) - actx.ctx = actx.ctx.WithBlockHeader(h) - - state := tendermint.ConsensusState{ - Root: commitment.NewMerkleRoot(commitID.Hash), - } - - actx.app.IBCKeeper.ClientKeeper.SetClientConsensusState(actx.ctx, clientID, 1, state) -} - -func (suite *KeeperTestSuite) createConnection( - actx *appContext, - clientID, - connectionID, - counterpartyClientID, - counterpartyConnectionID string, - state connectiontypes.State, -) { - prefix := actx.app.IBCKeeper.ConnectionKeeper.GetCommitmentPrefix() - - counterparty := connectiontypes.NewCounterparty( - counterpartyClientID, - counterpartyConnectionID, - commitmenttypes.NewMerklePrefix(prefix.Bytes()), - ) - - connection := connectiontypes.NewConnectionEnd( - state, - clientID, - counterparty, - connectiontypes.ExportedVersionsToProto(connectiontypes.GetCompatibleVersions()), - ) - - actx.app.IBCKeeper.ConnectionKeeper.SetConnection(actx.ctx, connectionID, connection) -} - -func (suite *KeeperTestSuite) createChannel(actx *appContext, portID string, chanID string, connID string, counterpartyPort string, counterpartyChan string, state channelexported.State) { - ch := channeltypes.Channel{ - State: state, - Ordering: testChannelOrder, - Counterparty: channeltypes.Counterparty{ - PortId: counterpartyPort, - ChannelId: counterpartyChan, - }, - ConnectionHops: []string{connID}, - Version: testChannelVersion, - } - - actx.app.IBCKeeper.ChannelKeeper.SetChannel(actx.ctx, portID, chanID, ch) - capName := ibctypes.ChannelCapabilityPath(portID, chanID) - cap, err := actx.app.ScopedIBCKeeper.NewCapability(actx.ctx, capName) - if err != nil { - suite.FailNow(err.Error()) - } - if err := actx.app.DNSKeeper.ClaimCapability(actx.ctx, cap, capName); err != nil { - suite.FailNow(err.Error()) - } -} - -func (suite *KeeperTestSuite) queryProof(actx *appContext, key []byte) (proof commitmentexported.Proof, height int64) { - res := actx.app.Query(abci.RequestQuery{ - Path: fmt.Sprintf("store/%s/key", ibctypes.StoreKey), - Data: key, - Prove: true, - }) - - height = res.Height - proof = commitment.MerkleProof{ - Proof: res.Proof, - } - - return -} - -func (suite *KeeperTestSuite) createClients( - srcClientID string, // clientID of dstapp - srcapp *appContext, - dstClientID string, // clientID of srcapp - dstapp *appContext, - skipIfClientExists bool, -) { - suite.createClient(srcapp, srcClientID, skipIfClientExists) - suite.createClient(dstapp, dstClientID, skipIfClientExists) -} - -func (suite *KeeperTestSuite) createConnections( - srcClientID string, - srcConnectionID string, - srcapp *appContext, - - dstClientID string, - dstConnectionID string, - dstapp *appContext, -) { - suite.createConnection(srcapp, srcClientID, srcConnectionID, dstClientID, dstConnectionID, connectiontypes.OPEN) - suite.createConnection(dstapp, dstClientID, dstConnectionID, srcClientID, srcConnectionID, connectiontypes.OPEN) -} - -func (suite *KeeperTestSuite) createChannels( - srcConnectionID string, srcapp *appContext, srcc ChannelInfo, - dstConnectionID string, dstapp *appContext, dstc ChannelInfo, -) { - suite.createChannel(srcapp, srcc.Port, srcc.Channel, srcConnectionID, dstc.Port, dstc.Channel, channelexported.OPEN) - suite.createChannel(dstapp, dstc.Port, dstc.Channel, dstConnectionID, srcc.Port, srcc.Channel, channelexported.OPEN) - - nextSeqSend := uint64(1) - srcapp.app.IBCKeeper.ChannelKeeper.SetNextSequenceSend(srcapp.ctx, srcc.Port, srcc.Channel, nextSeqSend) - dstapp.app.IBCKeeper.ChannelKeeper.SetNextSequenceSend(dstapp.ctx, dstc.Port, dstc.Channel, nextSeqSend) - - srcapp.channels[srcc] = dstc - dstapp.channels[dstc] = srcc -} - -func (suite *KeeperTestSuite) openChannels( - srcClientID string, // clientID of dstapp - srcConnectionID string, // id of the connection with dstapp - srcc ChannelInfo, // src's channel with dstapp - srcapp *appContext, - - dstClientID string, // clientID of srcapp - dstConnectionID string, // id of the connection with srcapp - dstc ChannelInfo, // dst's channel with srcapp - dstapp *appContext, - - skipIfClientExists bool, -) { - suite.createClients(srcClientID, srcapp, dstClientID, dstapp, skipIfClientExists) - suite.createConnections(srcClientID, srcConnectionID, srcapp, dstClientID, dstConnectionID, dstapp) - suite.createChannels(srcConnectionID, srcapp, srcc, dstConnectionID, dstapp, dstc) -} - -func (suite *KeeperTestSuite) createApp(chainID string) *appContext { - return suite.createAppWithHeader(tmproto.Header{ChainID: chainID}) -} - -func (suite *KeeperTestSuite) createAppWithHeader(header tmproto.Header) *appContext { - isCheckTx := false - app := simapp.SetupWithContractHandlerProvider(isCheckTx, simapp.DefaultAnteHandlerProvider) - ctx := app.BaseApp.NewContext(isCheckTx, header) - ctx = ctx.WithLogger(log.NewTMLogger(os.Stdout)) - if testing.Verbose() { - ctx = ctx.WithLogger( - log.NewFilter( - ctx.Logger(), - log.AllowDebugWith("module", "cross/cross"), - ), - ) - } else { - ctx = ctx.WithLogger( - log.NewFilter( - ctx.Logger(), - log.AllowErrorWith("module", "cross/cross"), - ), - ) - } - privVal := tmtypes.NewMockPV() - pub, err := privVal.GetPubKey() - if err != nil { - panic(err) - } - validator := tmtypes.NewValidator(pub, 1) - valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{validator}) - signers := []tmtypes.PrivValidator{privVal} - - actx := &appContext{ - chainID: header.GetChainID(), - cdc: app.Codec(), - ctx: ctx, - app: app, - valSet: valSet, - signers: signers, - channels: make(map[ChannelInfo]ChannelInfo), - } - - updateApp(actx, int(header.Height)) - - return actx -} - -func updateApp(actx *appContext, n int) { - for i := 0; i < n; i++ { - actx.app.Commit() - actx.app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{ChainID: actx.ctx.ChainID(), Height: actx.app.LastBlockHeight() + 1}}) - actx.ctx = actx.ctx.WithBlockHeader(tmproto.Header{ChainID: actx.ctx.ChainID()}) - } -} diff --git a/x/ibc-dns/server/keeper/relay_test.go b/x/ibc-dns/server/keeper/relay_test.go index cabc5d8..09a29af 100644 --- a/x/ibc-dns/server/keeper/relay_test.go +++ b/x/ibc-dns/server/keeper/relay_test.go @@ -3,14 +3,14 @@ package keeper_test import ( "testing" - ibcclienttypes "github.com/cosmos/cosmos-sdk/x/ibc/core/02-client/types" + clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/core/02-client/types" + channeltypes "github.com/cosmos/cosmos-sdk/x/ibc/core/04-channel/types" "github.com/stretchr/testify/suite" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/datachainlab/cosmos-sdk-interchain-dns/x/ibc-dns/common/types" - commontypes "github.com/datachainlab/cosmos-sdk-interchain-dns/x/ibc-dns/common/types" dnsservertypes "github.com/datachainlab/cosmos-sdk-interchain-dns/x/ibc-dns/server/types" servertypes "github.com/datachainlab/cosmos-sdk-interchain-dns/x/ibc-dns/server/types" + ibctesting "github.com/datachainlab/cosmos-sdk-interchain-dns/x/ibc-dns/testing" ) func TestDNSKeeperTestSuite(t *testing.T) { @@ -18,78 +18,80 @@ func TestDNSKeeperTestSuite(t *testing.T) { } type DNSKeeperTestSuite struct { - KeeperTestSuite + suite.Suite - app0 *appContext - app1 *appContext - dns0 *appContext + coordinator *ibctesting.Coordinator - chA0toA1 ChannelInfo - chA1toA0 ChannelInfo + app0 *ibctesting.TestChain + app1 *ibctesting.TestChain + dns0 *ibctesting.TestChain - chA0toD0 ChannelInfo - chD0toA0 ChannelInfo + chA0toA1 ibctesting.TestChannel + chA1toA0 ibctesting.TestChannel - chA1toD0 ChannelInfo - chD0toA1 ChannelInfo + chA0toD0 ibctesting.TestChannel + chD0toA0 ibctesting.TestChannel + + chA1toD0 ibctesting.TestChannel + chD0toA1 ibctesting.TestChannel } func (suite *DNSKeeperTestSuite) SetupTest() { - suite.dns0 = suite.createAppWithHeader(tmproto.Header{ChainID: "dns0"}) - suite.app0 = suite.createAppWithHeader(tmproto.Header{ChainID: "app0"}) - suite.app1 = suite.createAppWithHeader(tmproto.Header{ChainID: "app1"}) - - suite.chA0toA1 = ChannelInfo{"testportzeroone", "testchannelzeroone"} // app0 -> app1 - suite.chA1toA0 = ChannelInfo{"testportonezero", "testchannelonezero"} // app1 -> app0 - - suite.chA0toD0 = ChannelInfo{commontypes.PortID, "testchannelzerodns"} // app0 -> dns0 - suite.chD0toA0 = ChannelInfo{commontypes.PortID, "testchanneldnszero"} // dns0 -> app0 + t := suite.T() - suite.chA1toD0 = ChannelInfo{commontypes.PortID, "testchannelonedns"} // app1 -> dns0 - suite.chD0toA1 = ChannelInfo{commontypes.PortID, "testchanneldnsone"} // dns0 -> app1 + suite.dns0 = ibctesting.NewTestChain(t, "chain-dns0") + suite.app0 = ibctesting.NewTestChain(t, "chain-app0") + suite.app1 = ibctesting.NewTestChain(t, "chain-app1") + suite.coordinator = ibctesting.NewCoordinator(t, + suite.dns0, + suite.app0, + suite.app1, + ) } func (suite *DNSKeeperTestSuite) TestDomainRegistration() { require := suite.Require() - suite.openALLChannels(suite.app1.chainID, suite.app0.chainID) + suite.openALLChannels(suite.app1.ChainID, suite.app0.ChainID) const app0Name = "domain-app0" //* app0: Domain registration *// - suite.registerDomain(suite.app0, app0Name, suite.chA0toD0, suite.chD0toA0) + suite.registerDomain(suite.app0, app0Name, suite.chA0toD0) //* Try to register a duplicated domain *// - p0, err := suite.app1.app.DNSClientKeeper.SendPacketRegisterDomain( - suite.app1.ctx, + header, err := suite.app1.ConstructUpdateTMClientHeader(suite.dns0, suite.dns0.ChainID) + require.NoError(err) + p0, err := suite.app1.App.DNSClientKeeper.SendPacketRegisterDomain( + suite.app1.GetContext(), app0Name, // already used suite.chA1toD0.Port, suite.chA1toD0.Channel, []byte("memo"), - ibcclienttypes.Height{ - VersionHeight: 1, - VersionNumber: 0, - }, + clienttypes.NewHeight( + header.GetTrustedHeight().VersionNumber, + header.GetTrustedHeight().VersionHeight+1, + ), 0, ) require.NoError(err) - var data0 *dnsservertypes.RegisterDomainPacketData - require.NoError(servertypes.ModuleCdc.UnmarshalJSON(p0.Data, data0)) - require.Error(suite.dns0.app.DNSServerKeeper.ReceivePacketRegisterDomain( - suite.dns0.ctx, + var data0 dnsservertypes.RegisterDomainPacketData + require.NoError(suite.app1.App.AppCodec().UnmarshalJSON(p0.Data, &data0)) + require.Error(suite.dns0.App.DNSServerKeeper.ReceivePacketRegisterDomain( + suite.dns0.GetContext(), *p0, - data0, + &data0, )) - require.NoError(suite.app1.app.DNSClientKeeper.ReceiveRegisterDomainPacketAcknowledgement( - suite.app1.ctx, + require.NoError(suite.app1.App.DNSClientKeeper.ReceiveRegisterDomainPacketAcknowledgement( + suite.app1.GetContext(), dnsservertypes.STATUS_FAILED, app0Name, *p0, )) - _, found := suite.app1.app.DNSClientKeeper.GetSelfDomainName( - suite.app1.ctx, + _, found := suite.app1.App.DNSClientKeeper.GetSelfDomainName( + suite.app1.GetContext(), types.NewLocalDNSID(suite.chA1toD0.Port, suite.chA1toD0.Channel), ) require.False(found) @@ -103,9 +105,9 @@ func (suite *DNSKeeperTestSuite) TestDomainAssociation() { app1Name = "domain-app1" ) - suite.openALLChannels(suite.app1.chainID, suite.app0.chainID) - suite.registerDomain(suite.app0, app0Name, suite.chA0toD0, suite.chD0toA0) - suite.registerDomain(suite.app1, app1Name, suite.chA1toD0, suite.chD0toA1) + suite.openALLChannels(suite.app1.ChainID, suite.app0.ChainID) + suite.registerDomain(suite.app0, app0Name, suite.chA0toD0) + suite.registerDomain(suite.app1, app1Name, suite.chA1toD0) /// case0: normal /// @@ -113,26 +115,28 @@ func (suite *DNSKeeperTestSuite) TestDomainAssociation() { // app0: try to create a domain association { - packet, err := suite.app0.app.DNSClientKeeper.SendDomainAssociationCreatePacketData( - suite.app0.ctx, + header, err := suite.app0.ConstructUpdateTMClientHeader(suite.dns0, suite.dns0.ChainID) + require.NoError(err) + packet, err := suite.app0.App.DNSClientKeeper.SendDomainAssociationCreatePacketData( + suite.app0.GetContext(), dnsID0, - types.NewClientDomain(app0Name, suite.app1.chainID), - types.NewClientDomain(app1Name, suite.app0.chainID), - ibcclienttypes.Height{ - VersionHeight: 1, - VersionNumber: 0, - }, + types.NewClientDomain(app0Name, suite.app1.ChainID), + types.NewClientDomain(app1Name, suite.app0.ChainID), + clienttypes.NewHeight( + header.GetTrustedHeight().VersionNumber, + header.GetTrustedHeight().VersionHeight+1, + ), 0, ) require.NoError(err) require.Equal(suite.chA0toD0.Port, packet.GetSourcePort()) require.Equal(suite.chA0toD0.Channel, packet.GetSourceChannel()) - var data *dnsservertypes.DomainAssociationCreatePacketData - require.NoError(servertypes.ModuleCdc.UnmarshalJSON(packet.Data, data)) - ack, completed := suite.dns0.app.DNSServerKeeper.ReceiveDomainAssociationCreatePacketData( - suite.dns0.ctx, + var data dnsservertypes.DomainAssociationCreatePacketData + require.NoError(suite.app0.App.AppCodec().UnmarshalJSON(packet.Data, &data)) + ack, completed := suite.dns0.App.DNSServerKeeper.ReceiveDomainAssociationCreatePacketData( + suite.dns0.GetContext(), *packet, - data, + &data, ) require.False(completed) require.Equal(ack.Status, servertypes.STATUS_OK) @@ -141,26 +145,28 @@ func (suite *DNSKeeperTestSuite) TestDomainAssociation() { dnsID1 := types.NewLocalDNSID(suite.chA1toD0.Port, suite.chA1toD0.Channel) // app1: try to confirm the domain association { - packet, err := suite.app1.app.DNSClientKeeper.SendDomainAssociationCreatePacketData( - suite.app1.ctx, + header, err := suite.app1.ConstructUpdateTMClientHeader(suite.dns0, suite.dns0.ChainID) + require.NoError(err) + packet, err := suite.app1.App.DNSClientKeeper.SendDomainAssociationCreatePacketData( + suite.app1.GetContext(), dnsID1, - types.NewClientDomain(app1Name, suite.app0.chainID), - types.NewClientDomain(app0Name, suite.app1.chainID), - ibcclienttypes.Height{ - VersionHeight: 1, - VersionNumber: 0, - }, + types.NewClientDomain(app1Name, suite.app0.ChainID), + types.NewClientDomain(app0Name, suite.app1.ChainID), + clienttypes.NewHeight( + header.GetTrustedHeight().VersionNumber, + header.GetTrustedHeight().VersionHeight+1, + ), 0, ) require.NoError(err) require.Equal(suite.chA1toD0.Port, packet.GetSourcePort()) require.Equal(suite.chA1toD0.Channel, packet.GetSourceChannel()) - var data *dnsservertypes.DomainAssociationCreatePacketData - require.NoError(servertypes.ModuleCdc.UnmarshalJSON(packet.Data, data)) - ack, completed := suite.dns0.app.DNSServerKeeper.ReceiveDomainAssociationCreatePacketData( - suite.dns0.ctx, + var data dnsservertypes.DomainAssociationCreatePacketData + require.NoError(suite.app1.App.AppCodec().UnmarshalJSON(packet.Data, &data)) + ack, completed := suite.dns0.App.DNSServerKeeper.ReceiveDomainAssociationCreatePacketData( + suite.dns0.GetContext(), *packet, - data, + &data, ) require.True(completed) require.Equal(ack.Status, servertypes.STATUS_OK) @@ -168,152 +174,157 @@ func (suite *DNSKeeperTestSuite) TestDomainAssociation() { // dns0: create a domain association { - srcPacket, dstPacket, err := suite.dns0.app.DNSServerKeeper.CreateDomainAssociationResultPacketData( - suite.dns0.ctx, + header, err := suite.dns0.ConstructUpdateTMClientHeader(suite.dns0, suite.app0.ChainID) + require.NoError(err) + srcPacket, dstPacket, err := suite.dns0.App.DNSServerKeeper.CreateDomainAssociationResultPacketData( + suite.dns0.GetContext(), servertypes.STATUS_OK, - types.NewClientDomain(app1Name, suite.app0.chainID), - types.NewClientDomain(app0Name, suite.app1.chainID), - ibcclienttypes.Height{ - VersionHeight: 1, - VersionNumber: 0, - }, + types.NewClientDomain(app1Name, suite.app0.ChainID), + types.NewClientDomain(app0Name, suite.app1.ChainID), + clienttypes.NewHeight( + header.GetTrustedHeight().VersionNumber, + header.GetTrustedHeight().VersionHeight+1, + ), 0, ) require.NoError(err) - var srcData, dstData *servertypes.DomainAssociationResultPacketData - servertypes.ModuleCdc.MustUnmarshalJSON(srcPacket.Data, srcData) + var srcData, dstData servertypes.DomainAssociationResultPacketData + require.NoError(suite.dns0.App.AppCodec().UnmarshalJSON(srcPacket.Data, &srcData)) require.Equal(servertypes.STATUS_OK, srcData.Status) - require.Equal(suite.app1.chainID, srcData.ClientId) + require.Equal(suite.app1.ChainID, srcData.ClientId) require.Equal(types.NewLocalDomain(dnsID0, app0Name), srcData.CounterpartyDomain) - servertypes.ModuleCdc.MustUnmarshalJSON(dstPacket.Data, dstData) + require.NoError(suite.dns0.App.AppCodec().UnmarshalJSON(dstPacket.Data, &dstData)) require.Equal(servertypes.STATUS_OK, dstData.Status) - require.Equal(suite.app0.chainID, dstData.ClientId) + require.Equal(suite.app0.ChainID, dstData.ClientId) require.Equal(types.NewLocalDomain(dnsID1, app1Name), dstData.CounterpartyDomain) // receive the result of domain association require.NoError( - suite.app0.app.DNSClientKeeper.ReceiveDomainAssociationResultPacketData( - suite.app0.ctx, + suite.app0.App.DNSClientKeeper.ReceiveDomainAssociationResultPacketData( + suite.app0.GetContext(), *dstPacket, - dstData, + &dstData, ), ) require.NoError( - suite.app1.app.DNSClientKeeper.ReceiveDomainAssociationResultPacketData( - suite.app1.ctx, + suite.app1.App.DNSClientKeeper.ReceiveDomainAssociationResultPacketData( + suite.app1.GetContext(), *srcPacket, - srcData, + &srcData, ), ) - // app0: get a local DNS-ID using DNS - id1, found := suite.app0.app.DNSClientKeeper.ResolveDNSID( - suite.app0.ctx, + // app0: get a local DNS-Channel using DNS + id1, found := suite.app0.App.DNSClientKeeper.ResolveDNSID( + suite.app0.GetContext(), types.NewLocalDomain(dnsID0, app1Name), ) require.True(found) require.Equal(dnsID1, id1) - // app1: get a local DNS-ID using DNS - id0, found := suite.app1.app.DNSClientKeeper.ResolveDNSID( - suite.app1.ctx, + // app1: get a local DNS-Channel using DNS + id0, found := suite.app1.App.DNSClientKeeper.ResolveDNSID( + suite.app1.GetContext(), types.NewLocalDomain(dnsID1, app0Name), ) require.True(found) require.Equal(dnsID0, id0) // app0 - _, found = suite.app0.app.DNSClientKeeper.ResolveChannel( - suite.app0.ctx, + _, found = suite.app0.App.DNSClientKeeper.ResolveChannel( + suite.app0.GetContext(), types.NewLocalDomain(dnsID0, app1Name), suite.chA0toA1.Port, ) require.False(found) - require.NoError(suite.app0.app.DNSClientKeeper.SetDomainChannel( - suite.app0.ctx, + require.NoError(suite.app0.App.DNSClientKeeper.SetDomainChannel( + suite.app0.GetContext(), dnsID0, app1Name, types.NewChannel(suite.chA0toA1.Port, suite.chA0toA1.Channel, suite.chA1toA0.Port, suite.chA1toA0.Channel), )) // app0: resolve a channel using DNS - c0, found := suite.app0.app.DNSClientKeeper.ResolveChannel( - suite.app0.ctx, + c0, found := suite.app0.App.DNSClientKeeper.ResolveChannel( + suite.app0.GetContext(), types.NewLocalDomain(dnsID0, app1Name), suite.chA0toA1.Port, ) require.True(found) - exc0, found := suite.app0.app.IBCKeeper.ChannelKeeper.GetChannel(suite.app0.ctx, suite.chA0toA1.Port, suite.chA0toA1.Channel) + exc0, found := suite.app0.App.IBCKeeper.ChannelKeeper.GetChannel(suite.app0.GetContext(), suite.chA0toA1.Port, suite.chA0toA1.Channel) require.True(found) require.Equal(exc0, c0) // app1 - _, found = suite.app1.app.DNSClientKeeper.ResolveChannel( - suite.app1.ctx, + _, found = suite.app1.App.DNSClientKeeper.ResolveChannel( + suite.app1.GetContext(), types.NewLocalDomain(dnsID1, app0Name), suite.chA1toA0.Port, ) require.False(found) - require.NoError(suite.app1.app.DNSClientKeeper.SetDomainChannel( - suite.app1.ctx, + require.NoError(suite.app1.App.DNSClientKeeper.SetDomainChannel( + suite.app1.GetContext(), dnsID1, app0Name, types.NewChannel(suite.chA1toA0.Port, suite.chA1toA0.Channel, suite.chA0toA1.Port, suite.chA0toA1.Channel), )) // app1: resolve a channel using DNS - c1, found := suite.app1.app.DNSClientKeeper.ResolveChannel( - suite.app1.ctx, + c1, found := suite.app1.App.DNSClientKeeper.ResolveChannel( + suite.app1.GetContext(), types.NewLocalDomain(dnsID1, app0Name), suite.chA1toA0.Port, ) require.True(found) - exc1, found := suite.app1.app.IBCKeeper.ChannelKeeper.GetChannel(suite.app1.ctx, suite.chA1toA0.Port, suite.chA1toA0.Channel) + exc1, found := suite.app1.App.IBCKeeper.ChannelKeeper.GetChannel(suite.app1.GetContext(), suite.chA1toA0.Port, suite.chA1toA0.Channel) require.True(found) require.Equal(exc1, c1) - res, err := suite.dns0.app.DNSServerKeeper.QueryDomains(suite.dns0.ctx) + res, err := suite.dns0.App.DNSServerKeeper.QueryDomains(suite.dns0.GetContext()) require.NoError(err) require.Equal(2, len(res.Domains)) require.Equal(app0Name, res.Domains[0].Name) require.Equal(app1Name, res.Domains[1].Name) } - /// case1: A client referring to app1 in app0 is frozen, but DNS-ID is not changed /// + /// case1: A client referring to app1 in app0 is frozen, but DNS-Channel is not changed /// var ( - app1ClientID = suite.app1.chainID + "new" + app1ClientID = suite.app1.ChainID + "new" ) // update channel info - suite.chA0toA1 = ChannelInfo{suite.chA0toA1.Port, "testchannelzeroone" + "new"} // app0 -> app1 - suite.chA1toA0 = ChannelInfo{suite.chA1toA0.Port, "testchannelonezero" + "new"} // app1 -> app0 - suite.openAppChannels(app1ClientID, suite.app0.chainID, true) + err := suite.coordinator.CreateClient(suite.app0, suite.app1, app1ClientID, ibctesting.Tendermint) + require.NoError(err) + connA0toA1, connA1toA0 := suite.coordinator.CreateConnection(suite.app0, suite.app1, app1ClientID, suite.app0.ChainID) + suite.chA0toA1, suite.chA1toA0 = suite.coordinator.CreateMockChannels(suite.app0, suite.app1, connA0toA1, connA1toA0, channeltypes.UNORDERED) // app0: try to create a domain association { - packet, err := suite.app0.app.DNSClientKeeper.SendDomainAssociationCreatePacketData( - suite.app0.ctx, + header, err := suite.app0.ConstructUpdateTMClientHeader(suite.dns0, suite.dns0.ChainID) + require.NoError(err) + packet, err := suite.app0.App.DNSClientKeeper.SendDomainAssociationCreatePacketData( + suite.app0.GetContext(), dnsID0, types.NewClientDomain(app0Name, app1ClientID), - types.NewClientDomain(app1Name, suite.app0.chainID), - ibcclienttypes.Height{ - VersionHeight: 1, - VersionNumber: 0, - }, + types.NewClientDomain(app1Name, suite.app0.ChainID), + clienttypes.NewHeight( + header.GetTrustedHeight().VersionNumber, + header.GetTrustedHeight().VersionHeight+1, + ), 0, ) require.NoError(err) require.Equal(suite.chA0toD0.Port, packet.GetSourcePort()) require.Equal(suite.chA0toD0.Channel, packet.GetSourceChannel()) - var data *dnsservertypes.DomainAssociationCreatePacketData - require.NoError(servertypes.ModuleCdc.UnmarshalJSON(packet.Data, data)) - ack, completed := suite.dns0.app.DNSServerKeeper.ReceiveDomainAssociationCreatePacketData( - suite.dns0.ctx, + var data dnsservertypes.DomainAssociationCreatePacketData + require.NoError(suite.app0.App.AppCodec().UnmarshalJSON(packet.Data, &data)) + ack, completed := suite.dns0.App.DNSServerKeeper.ReceiveDomainAssociationCreatePacketData( + suite.dns0.GetContext(), *packet, - data, + &data, ) require.False(completed) require.Equal(ack.Status, servertypes.STATUS_OK) @@ -321,26 +332,28 @@ func (suite *DNSKeeperTestSuite) TestDomainAssociation() { // app1: try to confirm the domain association { - packet, err := suite.app1.app.DNSClientKeeper.SendDomainAssociationCreatePacketData( - suite.app1.ctx, + header, err := suite.app1.ConstructUpdateTMClientHeader(suite.dns0, suite.dns0.ChainID) + require.NoError(err) + packet, err := suite.app1.App.DNSClientKeeper.SendDomainAssociationCreatePacketData( + suite.app1.GetContext(), dnsID1, - types.NewClientDomain(app1Name, suite.app0.chainID), + types.NewClientDomain(app1Name, suite.app0.ChainID), types.NewClientDomain(app0Name, app1ClientID), - ibcclienttypes.Height{ - VersionHeight: 1, - VersionNumber: 0, - }, + clienttypes.NewHeight( + header.GetTrustedHeight().VersionNumber, + header.GetTrustedHeight().VersionHeight+1, + ), 0, ) require.NoError(err) require.Equal(suite.chA1toD0.Port, packet.GetSourcePort()) require.Equal(suite.chA1toD0.Channel, packet.GetSourceChannel()) - var data *dnsservertypes.DomainAssociationCreatePacketData - require.NoError(servertypes.ModuleCdc.UnmarshalJSON(packet.Data, data)) - ack, completed := suite.dns0.app.DNSServerKeeper.ReceiveDomainAssociationCreatePacketData( - suite.dns0.ctx, + var data dnsservertypes.DomainAssociationCreatePacketData + require.NoError(suite.app1.App.AppCodec().UnmarshalJSON(packet.Data, &data)) + ack, completed := suite.dns0.App.DNSServerKeeper.ReceiveDomainAssociationCreatePacketData( + suite.dns0.GetContext(), *packet, - data, + &data, ) require.True(completed) require.Equal(ack.Status, servertypes.STATUS_OK) @@ -348,57 +361,59 @@ func (suite *DNSKeeperTestSuite) TestDomainAssociation() { // dns0: create a domain association { - srcPacket, dstPacket, err := suite.dns0.app.DNSServerKeeper.CreateDomainAssociationResultPacketData( - suite.dns0.ctx, + header, err := suite.dns0.ConstructUpdateTMClientHeader(suite.dns0, suite.app0.ChainID) + require.NoError(err) + srcPacket, dstPacket, err := suite.dns0.App.DNSServerKeeper.CreateDomainAssociationResultPacketData( + suite.dns0.GetContext(), servertypes.STATUS_OK, - types.NewClientDomain(app1Name, suite.app0.chainID), + types.NewClientDomain(app1Name, suite.app0.ChainID), types.NewClientDomain(app0Name, app1ClientID), - ibcclienttypes.Height{ - VersionHeight: 1, - VersionNumber: 0, - }, + clienttypes.NewHeight( + header.GetTrustedHeight().VersionNumber, + header.GetTrustedHeight().VersionHeight+1, + ), 0, ) require.NoError(err) - var srcData, dstData *servertypes.DomainAssociationResultPacketData - servertypes.ModuleCdc.MustUnmarshalJSON(srcPacket.Data, srcData) + var srcData, dstData servertypes.DomainAssociationResultPacketData + require.NoError(suite.dns0.App.AppCodec().UnmarshalJSON(srcPacket.Data, &srcData)) require.Equal(servertypes.STATUS_OK, srcData.Status) require.Equal(app1ClientID, srcData.ClientId) require.Equal(types.NewLocalDomain(dnsID0, app0Name), srcData.CounterpartyDomain) - servertypes.ModuleCdc.MustUnmarshalJSON(dstPacket.Data, dstData) + require.NoError(suite.dns0.App.AppCodec().UnmarshalJSON(dstPacket.Data, &dstData)) require.Equal(servertypes.STATUS_OK, dstData.Status) - require.Equal(suite.app0.chainID, dstData.ClientId) + require.Equal(suite.app0.ChainID, dstData.ClientId) require.Equal(types.NewLocalDomain(dnsID1, app1Name), dstData.CounterpartyDomain) // receive the result of domain association require.NoError( - suite.app0.app.DNSClientKeeper.ReceiveDomainAssociationResultPacketData( - suite.app0.ctx, + suite.app0.App.DNSClientKeeper.ReceiveDomainAssociationResultPacketData( + suite.app0.GetContext(), *dstPacket, - dstData, + &dstData, ), ) require.NoError( - suite.app1.app.DNSClientKeeper.ReceiveDomainAssociationResultPacketData( - suite.app1.ctx, + suite.app1.App.DNSClientKeeper.ReceiveDomainAssociationResultPacketData( + suite.app1.GetContext(), *srcPacket, - srcData, + &srcData, ), ) // app0: get a local DNS-ID using DNS - id1, found := suite.app0.app.DNSClientKeeper.ResolveDNSID( - suite.app0.ctx, + id1, found := suite.app0.App.DNSClientKeeper.ResolveDNSID( + suite.app0.GetContext(), types.NewLocalDomain(dnsID0, app1Name), ) require.True(found) require.Equal(dnsID1, id1) // app1: get a local DNS-ID using DNS - id0, found := suite.app1.app.DNSClientKeeper.ResolveDNSID( - suite.app1.ctx, + id0, found := suite.app1.App.DNSClientKeeper.ResolveDNSID( + suite.app1.GetContext(), types.NewLocalDomain(dnsID1, app0Name), ) require.True(found) @@ -406,96 +421,103 @@ func (suite *DNSKeeperTestSuite) TestDomainAssociation() { // app0 // resolve the domain name to the old channel - _, found = suite.app0.app.DNSClientKeeper.ResolveChannel( - suite.app0.ctx, + _, found = suite.app0.App.DNSClientKeeper.ResolveChannel( + suite.app0.GetContext(), types.NewLocalDomain(dnsID0, app1Name), suite.chA0toA1.Port, ) require.True(found) - require.NoError(suite.app0.app.DNSClientKeeper.SetDomainChannel( - suite.app0.ctx, + require.NoError(suite.app0.App.DNSClientKeeper.SetDomainChannel( + suite.app0.GetContext(), dnsID0, app1Name, types.NewChannel(suite.chA0toA1.Port, suite.chA0toA1.Channel, suite.chA1toA0.Port, suite.chA1toA0.Channel), )) // app0: resolve a channel using DNS - c0, found := suite.app0.app.DNSClientKeeper.ResolveChannel( - suite.app0.ctx, + c0, found := suite.app0.App.DNSClientKeeper.ResolveChannel( + suite.app0.GetContext(), types.NewLocalDomain(dnsID0, app1Name), suite.chA0toA1.Port, ) require.True(found) - exc0, found := suite.app0.app.IBCKeeper.ChannelKeeper.GetChannel(suite.app0.ctx, suite.chA0toA1.Port, suite.chA0toA1.Channel) + exc0, found := suite.app0.App.IBCKeeper.ChannelKeeper.GetChannel(suite.app0.GetContext(), suite.chA0toA1.Port, suite.chA0toA1.Channel) require.True(found) require.Equal(exc0, c0) // app1 // resolve the domain name to the old channel - _, found = suite.app1.app.DNSClientKeeper.ResolveChannel( - suite.app1.ctx, + _, found = suite.app1.App.DNSClientKeeper.ResolveChannel( + suite.app1.GetContext(), types.NewLocalDomain(dnsID1, app0Name), suite.chA1toA0.Port, ) require.True(found) - require.NoError(suite.app1.app.DNSClientKeeper.SetDomainChannel( - suite.app1.ctx, + require.NoError(suite.app1.App.DNSClientKeeper.SetDomainChannel( + suite.app1.GetContext(), dnsID1, app0Name, types.NewChannel(suite.chA1toA0.Port, suite.chA1toA0.Channel, suite.chA0toA1.Port, suite.chA0toA1.Channel), )) // app1: resolve a channel using DNS - c1, found := suite.app1.app.DNSClientKeeper.ResolveChannel( - suite.app1.ctx, + c1, found := suite.app1.App.DNSClientKeeper.ResolveChannel( + suite.app1.GetContext(), types.NewLocalDomain(dnsID1, app0Name), suite.chA1toA0.Port, ) require.True(found) - exc1, found := suite.app1.app.IBCKeeper.ChannelKeeper.GetChannel(suite.app1.ctx, suite.chA1toA0.Port, suite.chA1toA0.Channel) + exc1, found := suite.app1.App.IBCKeeper.ChannelKeeper.GetChannel(suite.app1.GetContext(), suite.chA1toA0.Port, suite.chA1toA0.Channel) require.True(found) require.Equal(exc1, c1) } } -func (suite *DNSKeeperTestSuite) registerDomain(app *appContext, name string, srcci, dstci ChannelInfo) { +func (suite *DNSKeeperTestSuite) registerDomain( + chain *ibctesting.TestChain, + name string, + srcch ibctesting.TestChannel, +) { require := suite.Require() - p0, err := app.app.DNSClientKeeper.SendPacketRegisterDomain( - app.ctx, + header, err := chain.ConstructUpdateTMClientHeader(suite.dns0, suite.dns0.ChainID) + require.NoError(err) + + p0, err := chain.App.DNSClientKeeper.SendPacketRegisterDomain( + chain.GetContext(), name, - srcci.Port, - srcci.Channel, + srcch.Port, + srcch.Channel, []byte("memo"), - ibcclienttypes.Height{ - VersionHeight: 1, - VersionNumber: 0, - }, + clienttypes.NewHeight( + header.GetTrustedHeight().VersionNumber, + header.GetTrustedHeight().VersionHeight+1, + ), 0, ) require.NoError(err) - var data0 *dnsservertypes.RegisterDomainPacketData - require.NoError(servertypes.ModuleCdc.UnmarshalJSON(p0.Data, data0)) - require.NoError(suite.dns0.app.DNSServerKeeper.ReceivePacketRegisterDomain( - suite.dns0.ctx, + var data0 dnsservertypes.RegisterDomainPacketData + require.NoError(chain.App.AppCodec().UnmarshalJSON(p0.Data, &data0)) + require.NoError(suite.dns0.App.DNSServerKeeper.ReceivePacketRegisterDomain( + suite.dns0.GetContext(), *p0, - data0, + &data0, )) - require.NoError(app.app.DNSClientKeeper.ReceiveRegisterDomainPacketAcknowledgement( - app.ctx, + require.NoError(chain.App.DNSClientKeeper.ReceiveRegisterDomainPacketAcknowledgement( + chain.GetContext(), dnsservertypes.STATUS_OK, name, *p0, )) - dnsID := types.NewLocalDNSID(srcci.Port, srcci.Channel) + dnsID := types.NewLocalDNSID(srcch.Port, srcch.Channel) - res, err := suite.dns0.app.DNSServerKeeper.QueryDomain(suite.dns0.ctx, servertypes.QueryDomainRequest{Name: name}) + res, err := suite.dns0.App.DNSServerKeeper.QueryDomain(suite.dns0.GetContext(), servertypes.QueryDomainRequest{Name: name}) require.NoError(err) require.Equal(dnsID, res.Domain.DnsId) - name, found := app.app.DNSClientKeeper.GetSelfDomainName( - app.ctx, + name, found := chain.App.DNSClientKeeper.GetSelfDomainName( + chain.GetContext(), dnsID, ) require.True(found) @@ -503,49 +525,46 @@ func (suite *DNSKeeperTestSuite) registerDomain(app *appContext, name string, sr } func (suite *DNSKeeperTestSuite) openALLChannels(srcClientID, dstClientID string) { - suite.openAppChannels(srcClientID, dstClientID, false) + suite.chA0toA1, suite.chA1toA0 = suite.openAppChannels(srcClientID, dstClientID) - suite.openChannels( - suite.dns0.chainID, - dstClientID+suite.dns0.chainID, - suite.chA0toD0, + _, _, connA0toD0, connD0toA0 := suite.coordinator.SetupClientConnections( suite.app0, - + suite.dns0, + suite.dns0.ChainID, dstClientID, - suite.dns0.chainID+dstClientID, - suite.chD0toA0, + ibctesting.Tendermint, + ) + suite.chA0toD0, suite.chD0toA0 = suite.coordinator.CreateDNSChannels( + suite.app0, suite.dns0, - - false, + connA0toD0, + connD0toA0, + channeltypes.UNORDERED, ) - suite.openChannels( - suite.dns0.chainID, - srcClientID+suite.dns0.chainID, - suite.chA1toD0, + _, _, connA1toD0, connD0toA1 := suite.coordinator.SetupClientConnections( suite.app1, - + suite.dns0, + suite.dns0.ChainID, srcClientID, - suite.dns0.chainID+srcClientID, - suite.chD0toA1, + ibctesting.Tendermint, + ) + suite.chA1toD0, suite.chD0toA1 = suite.coordinator.CreateDNSChannels( + suite.app1, suite.dns0, - - false, + connA1toD0, + connD0toA1, + channeltypes.UNORDERED, ) } -func (suite *DNSKeeperTestSuite) openAppChannels(srcClientID, dstClientID string, skipIfClientExists bool) { - suite.openChannels( - srcClientID, - dstClientID+srcClientID, - suite.chA0toA1, +func (suite *DNSKeeperTestSuite) openAppChannels(srcClientID, dstClientID string) (ibctesting.TestChannel, ibctesting.TestChannel) { + _, _, connA0toA1, connA1toA0 := suite.coordinator.SetupClientConnections( suite.app0, - - dstClientID, - srcClientID+dstClientID, - suite.chA1toA0, suite.app1, - - skipIfClientExists, + srcClientID, + dstClientID, + ibctesting.Tendermint, ) + return suite.coordinator.CreateMockChannels(suite.app0, suite.app1, connA0toA1, connA1toA0, channeltypes.UNORDERED) } diff --git a/x/ibc-dns/testing/chain.go b/x/ibc-dns/testing/chain.go new file mode 100644 index 0000000..53108e3 --- /dev/null +++ b/x/ibc-dns/testing/chain.go @@ -0,0 +1,938 @@ +package ibctesting + +import ( + "bytes" + "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/tmhash" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + tmprotoversion "github.com/tendermint/tendermint/proto/tendermint/version" + tmtypes "github.com/tendermint/tendermint/types" + tmversion "github.com/tendermint/tendermint/version" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + ibctransfertypes "github.com/cosmos/cosmos-sdk/x/ibc/applications/transfer/types" + clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/core/02-client/types" + connectiontypes "github.com/cosmos/cosmos-sdk/x/ibc/core/03-connection/types" + channeltypes "github.com/cosmos/cosmos-sdk/x/ibc/core/04-channel/types" + commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/core/23-commitment/types" + host "github.com/cosmos/cosmos-sdk/x/ibc/core/24-host" + "github.com/cosmos/cosmos-sdk/x/ibc/core/exported" + "github.com/cosmos/cosmos-sdk/x/ibc/core/types" + solomachinetypes "github.com/cosmos/cosmos-sdk/x/ibc/light-clients/06-solomachine/types" + ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/light-clients/07-tendermint/types" + "github.com/cosmos/cosmos-sdk/x/ibc/testing/mock" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + "github.com/datachainlab/cosmos-sdk-interchain-dns/example/simapp" + commontypes "github.com/datachainlab/cosmos-sdk-interchain-dns/x/ibc-dns/common/types" +) + +const ( + // client types + Tendermint = ibctmtypes.Tendermint + SoloMachine = solomachinetypes.SoloMachine + + // Default params constants 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 + + DefaultChannelVersion = commontypes.Version + InvalidID = "IDisInvalid" + + ConnectionIDPrefix = "conn" + ChannelIDPrefix = "chan" + + DNSPort = commontypes.PortID + MockPort = mock.ModuleName + + // used for testing UpdateClientProposal + Title = "title" + Description = "description" +) + +var ( + DefaultConsensusParams = simapp.DefaultConsensusParams + + DefaultOpenInitVersion *connectiontypes.Version + + // Default params variables used to create a TM client + DefaultTrustLevel ibctmtypes.Fraction = ibctmtypes.DefaultTrustLevel + TestHash = tmhash.Sum([]byte("TESTING HASH")) + TestCoin = sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)) + + UpgradePath = fmt.Sprintf("%s/%s", "upgrade", "upgradedClient") + + ConnectionVersion = connectiontypes.ExportedVersionsToProto(connectiontypes.GetCompatibleVersions())[0] + + MockAcknowledgement = mock.MockAcknowledgement + MockCommitment = mock.MockCommitment + + // Conditionals for expected output of executing messages. + // Change values to false to test messages expected to fail. + // Reset to true otherwise successful messages will error. + // Use in rare cases, will be deprecated in favor of better + // dev ux. + ExpSimPassSend = true + ExpPassSend = true +) + +// 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 tmproto.Header // header for current block height + QueryServer types.QueryServer + TxConfig client.TxConfig + Codec codec.BinaryMarshaler + + 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 +} + +// 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 := mock.NewPV() + pubKey, err := privVal.GetPubKey() + require.NoError(t, err) + + // create validator set with single validator + validator := tmtypes.NewValidator(pubKey.(cryptotypes.IntoTmPubKey).AsTmPubKey(), 1) + valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{validator}) + signers := []tmtypes.PrivValidator{privVal} + + // 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, sdk.NewInt(100000000000000))), + } + + app := simapp.SetupWithGenesisValSet(t, valSet, []authtypes.GenesisAccount{acc}, balance) + + // create current header and call begin block + header := tmproto.Header{ + ChainID: chainID, + Height: 1, + Time: globalStartTime, + } + + txConfig := simapp.MakeEncodingConfig().TxConfig + + // create an account to send transactions from + chain := &TestChain{ + t: t, + ChainID: chainID, + App: app, + CurrentHeader: header, + QueryServer: app.IBCKeeper, + TxConfig: txConfig, + Codec: app.AppCodec(), + Vals: valSet, + Signers: signers, + senderPrivKey: senderPrivKey, + SenderAccount: acc, + Connections: make([]*TestConnection, 0), + } + + cap := chain.App.IBCKeeper.PortKeeper.BindPort(chain.GetContext(), MockPort) + err = chain.App.ScopedIBCMockKeeper.ClaimCapability(chain.GetContext(), cap, host.PortPath(MockPort)) + require.NoError(t, err) + + chain.NextBlock() + + return chain +} + +// GetContext returns the current context for the application. +func (chain *TestChain) GetContext() sdk.Context { + ctx := chain.App.BaseApp.NewContext(false, chain.CurrentHeader) + return ctx.WithConsensusParams(DefaultConsensusParams) +} + +// 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 proof will succeed on a tendermint verifier. +func (chain *TestChain) QueryProof(key []byte) ([]byte, clienttypes.Height) { + res := chain.App.Query(abci.RequestQuery{ + Path: fmt.Sprintf("store/%s/key", host.StoreKey), + Height: chain.App.LastBlockHeight() - 1, + Data: key, + Prove: true, + }) + + merkleProof := commitmenttypes.MerkleProof{ + Proof: res.ProofOps, + } + + proof, err := chain.App.AppCodec().MarshalBinaryBare(&merkleProof) + require.NoError(chain.t, err) + + version := clienttypes.ParseChainID(chain.ChainID) + + // proof height + 1 is returned as the proof created corresponds to the height the proof + // was created in the IAVL tree. Tendermint and subsequently the clients that rely on it + // have heights 1 above the IAVL tree. Thus we return proof height + 1 + return proof, clienttypes.NewHeight(version, uint64(res.Height)+1) +} + +// QueryUpgradeProof performs an abci query with the given key and returns the proto encoded merkle proof +// for the query and the height at which the proof will succeed on a tendermint verifier. +func (chain *TestChain) QueryUpgradeProof(key []byte, height uint64) ([]byte, clienttypes.Height) { + res := chain.App.Query(abci.RequestQuery{ + Path: "store/upgrade/key", + Height: int64(height - 1), + Data: key, + Prove: true, + }) + + merkleProof := commitmenttypes.MerkleProof{ + Proof: res.ProofOps, + } + + proof, err := chain.App.AppCodec().MarshalBinaryBare(&merkleProof) + require.NoError(chain.t, err) + + version := clienttypes.ParseChainID(chain.ChainID) + + // proof height + 1 is returned as the proof created corresponds to the height the proof + // was created in the IAVL tree. Tendermint and subsequently the clients that rely on it + // have heights 1 above the IAVL tree. Thus we return proof height + 1 + return proof, clienttypes.NewHeight(version, uint64(res.Height+1)) +} + +// QueryClientStateProof performs and abci query for a client state +// stored with a given clientID and returns the ClientState along with the proof +func (chain *TestChain) QueryClientStateProof(clientID string) (exported.ClientState, []byte) { + // retrieve client state to provide proof for + clientState, found := chain.App.IBCKeeper.ClientKeeper.GetClientState(chain.GetContext(), clientID) + require.True(chain.t, found) + + clientKey := host.FullKeyClientPath(clientID, host.KeyClientState()) + proofClient, _ := chain.QueryProof(clientKey) + + return clientState, proofClient +} + +// QueryConsensusStateProof performs an abci query for a consensus state +// stored on the given clientID. The proof and consensusHeight are returned. +func (chain *TestChain) QueryConsensusStateProof(clientID string) ([]byte, clienttypes.Height) { + clientState := chain.GetClientState(clientID) + + consensusHeight := clientState.GetLatestHeight().(clienttypes.Height) + consensusKey := host.FullKeyClientPath(clientID, host.KeyConsensusState(consensusHeight)) + proofConsensus, _ := chain.QueryProof(consensusKey) + + return proofConsensus, consensusHeight +} + +// 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 + // use nil trusted fields + chain.LastHeader = chain.CurrentTMClientHeader() + + // increment the current header + chain.CurrentHeader = tmproto.Header{ + ChainID: chain.ChainID, + Height: chain.App.LastBlockHeight() + 1, + AppHash: chain.App.LastCommitID().Hash, + // NOTE: the time is increased by the coordinator to maintain time synchrony amongst + // chains. + Time: chain.CurrentHeader.Time, + ValidatorsHash: chain.Vals.Hash(), + NextValidatorsHash: chain.Vals.Hash(), + } + + chain.App.BeginBlock(abci.RequestBeginBlock{Header: chain.CurrentHeader}) + +} + +// sendMsgs delivers a transaction through the application without returning the result. +func (chain *TestChain) sendMsgs(msgs ...sdk.Msg) error { + _, err := chain.SendMsgs(msgs...) + return err +} + +// SendMsgs delivers a transaction through the application. It updates the senders sequence +// number and updates the TestChain's headers. It returns the result and error if one +// occurred. +func (chain *TestChain) SendMsgs(msgs ...sdk.Msg) (*sdk.Result, error) { + _, r, err := simapp.SignCheckDeliver( + chain.t, + chain.TxConfig, + chain.App.BaseApp, + chain.GetContext().BlockHeader(), + msgs, + chain.ChainID, + []uint64{chain.SenderAccount.GetAccountNumber()}, + []uint64{chain.SenderAccount.GetSequence()}, + ExpSimPassSend, ExpPassSend, chain.senderPrivKey, + ) + if err != nil { + return nil, err + } + + // SignCheckDeliver calls app.Commit() + chain.NextBlock() + + // increment sequence for successful transaction execution + chain.SenderAccount.SetSequence(chain.SenderAccount.GetSequence() + 1) + + return r, nil +} + +// GetClientState retrieves the client state for the provided clientID. The client is +// expected to exist otherwise testing will fail. +func (chain *TestChain) GetClientState(clientID string) exported.ClientState { + clientState, found := chain.App.IBCKeeper.ClientKeeper.GetClientState(chain.GetContext(), clientID) + require.True(chain.t, found) + + return clientState +} + +// GetConsensusState retrieves the consensus state for the provided clientID and height. +// It will return a success boolean depending on if consensus state exists or not. +func (chain *TestChain) GetConsensusState(clientID string, height exported.Height) (exported.ConsensusState, bool) { + return chain.App.IBCKeeper.ClientKeeper.GetClientConsensusState(chain.GetContext(), clientID, height) +} + +// GetValsAtHeight will return the validator set of the chain at a given height. It will return +// a success boolean depending on if the validator set exists or not at that height. +func (chain *TestChain) GetValsAtHeight(height int64) (*tmtypes.ValidatorSet, bool) { + histInfo, ok := chain.App.StakingKeeper.GetHistoricalInfo(chain.GetContext(), height) + if !ok { + return nil, false + } + + valSet := stakingtypes.Validators(histInfo.Valset) + + tmValidators, err := valSet.ToTmValidators() + if err != nil { + panic(err) + } + return tmtypes.NewValidatorSet(tmValidators), true +} + +// GetConnection retrieves an IBC Connection for the provided TestConnection. The +// connection is expected to exist otherwise testing will fail. +func (chain *TestChain) GetConnection(testConnection *TestConnection) connectiontypes.ConnectionEnd { + connection, found := chain.App.IBCKeeper.ConnectionKeeper.GetConnection(chain.GetContext(), testConnection.ID) + require.True(chain.t, found) + + return connection +} + +// GetChannel retrieves an IBC Channel for the provided TestChannel. The channel +// is expected to exist otherwise testing will fail. +func (chain *TestChain) GetChannel(testChannel TestChannel) channeltypes.Channel { + channel, found := chain.App.IBCKeeper.ChannelKeeper.GetChannel(chain.GetContext(), testChannel.Port, testChannel.Channel) + require.True(chain.t, found) + + return channel +} + +// GetAcknowledgement retrieves an acknowledgement for the provided packet. If the +// acknowledgement does not exist then testing will fail. +func (chain *TestChain) GetAcknowledgement(packet exported.PacketI) []byte { + ack, found := chain.App.IBCKeeper.ChannelKeeper.GetPacketAcknowledgement(chain.GetContext(), packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + require.True(chain.t, found) + + return ack +} + +// GetPrefix returns the prefix for used by a chain in connection creation +func (chain *TestChain) GetPrefix() commitmenttypes.MerklePrefix { + return commitmenttypes.NewMerklePrefix(chain.App.IBCKeeper.ConnectionKeeper.GetCommitmentPrefix().Bytes()) +} + +//// NewClientID appends a new clientID string in the format: +//// ClientFor +//func (chain *TestChain) NewClientID(counterpartyChainID string) string { +// clientID := "client" + strconv.Itoa(len(chain.ClientIDs)) + "For" + counterpartyChainID +// chain.ClientIDs = append(chain.ClientIDs, clientID) +// return clientID +//} + +// AddTestConnection appends a new TestConnection which contains references +// to the connection id, client id and counterparty client id. +func (chain *TestChain) AddTestConnection(clientID, counterpartyClientID string) *TestConnection { + conn := chain.ConstructNextTestConnection(clientID, counterpartyClientID) + + chain.Connections = append(chain.Connections, conn) + return conn +} + +// ConstructNextTestConnection constructs the next test connection to be +// created given a clientID and counterparty clientID. The connection id +// format: -conn +func (chain *TestChain) ConstructNextTestConnection(clientID, counterpartyClientID string) *TestConnection { + connectionID := fmt.Sprintf("%s-%s%d", chain.ChainID, ConnectionIDPrefix, len(chain.Connections)) + return &TestConnection{ + ID: connectionID, + ClientID: clientID, + NextChannelVersion: DefaultChannelVersion, + CounterpartyClientID: counterpartyClientID, + } +} + +// GetFirstTestConnection returns the first test connection for a given clientID. +// The connection may or may not exist in the chain state. +func (chain *TestChain) GetFirstTestConnection(clientID, counterpartyClientID string) *TestConnection { + if len(chain.Connections) > 0 { + return chain.Connections[0] + } + + return chain.ConstructNextTestConnection(clientID, counterpartyClientID) +} + +// ConstructMsgCreateClient constructs a message to create a new client state (tendermint or solomachine). +// NOTE: a solo machine client will be created with an empty diversifier. +func (chain *TestChain) ConstructMsgCreateClient(counterparty *TestChain, clientID string, clientType string) *clienttypes.MsgCreateClient { + var ( + clientState exported.ClientState + consensusState exported.ConsensusState + ) + + switch clientType { + case Tendermint: + height := counterparty.LastHeader.GetHeight().(clienttypes.Height) + clientState = ibctmtypes.NewClientState( + counterparty.ChainID, DefaultTrustLevel, TrustingPeriod, UnbondingPeriod, MaxClockDrift, + height, counterparty.App.GetConsensusParams(counterparty.GetContext()), commitmenttypes.GetSDKSpecs(), + UpgradePath, false, false, + ) + consensusState = counterparty.LastHeader.ConsensusState() + case SoloMachine: + solo := NewSolomachine(chain.t, chain.Codec, clientID, "", 1) + clientState = solo.ClientState() + consensusState = solo.ConsensusState() + default: + chain.t.Fatalf("unsupported client state type %s", clientType) + } + + msg, err := clienttypes.NewMsgCreateClient( + clientID, clientState, consensusState, chain.SenderAccount.GetAddress(), + ) + require.NoError(chain.t, err) + return msg +} + +// 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 := chain.ConstructMsgCreateClient(counterparty, clientID, Tendermint) + return chain.sendMsgs(msg) +} + +// UpdateTMClient will construct and execute a 07-tendermint MsgUpdateClient. The counterparty +// client will be updated on the (target) chain. UpdateTMClient mocks the relayer flow +// necessary for updating a Tendermint client. +func (chain *TestChain) UpdateTMClient(counterparty *TestChain, clientID string) error { + header, err := chain.ConstructUpdateTMClientHeader(counterparty, clientID) + require.NoError(chain.t, err) + + msg, err := clienttypes.NewMsgUpdateClient( + clientID, header, + chain.SenderAccount.GetAddress(), + ) + require.NoError(chain.t, err) + + return chain.sendMsgs(msg) +} + +// ConstructUpdateTMClientHeader will construct a valid 07-tendermint Header to update the +// light client on the source chain. +func (chain *TestChain) ConstructUpdateTMClientHeader(counterparty *TestChain, clientID string) (*ibctmtypes.Header, error) { + header := counterparty.LastHeader + // Relayer must query for LatestHeight on client to get TrustedHeight + trustedHeight := chain.GetClientState(clientID).GetLatestHeight().(clienttypes.Height) + var ( + tmTrustedVals *tmtypes.ValidatorSet + ok bool + ) + // Once we get TrustedHeight from client, we must query the validators from the counterparty chain + // If the LatestHeight == LastHeader.Height, then TrustedValidators are current validators + // If LatestHeight < LastHeader.Height, we can query the historical validator set from HistoricalInfo + if trustedHeight == counterparty.LastHeader.GetHeight() { + tmTrustedVals = counterparty.Vals + } else { + // NOTE: We need to get validators from counterparty at height: trustedHeight+1 + // since the last trusted validators for a header at height h + // is the NextValidators at h+1 committed to in header h by + // NextValidatorsHash + tmTrustedVals, ok = counterparty.GetValsAtHeight(int64(trustedHeight.VersionHeight + 1)) + if !ok { + return nil, sdkerrors.Wrapf(ibctmtypes.ErrInvalidHeaderHeight, "could not retrieve trusted validators at trustedHeight: %d", trustedHeight) + } + } + // inject trusted fields into last header + // for now assume version number is 0 + header.TrustedHeight = trustedHeight + + trustedVals, err := tmTrustedVals.ToProto() + if err != nil { + return nil, err + } + header.TrustedValidators = trustedVals + + return header, nil + +} + +// ExpireClient fast forwards the chain's block time by the provided amount of time which will +// expire any clients with a trusting period less than or equal to this amount of time. +func (chain *TestChain) ExpireClient(amount time.Duration) { + chain.CurrentHeader.Time = chain.CurrentHeader.Time.Add(amount) +} + +// CurrentTMClientHeader creates a TM header using the current header parameters +// on the chain. The trusted fields in the header are set to nil. +func (chain *TestChain) CurrentTMClientHeader() *ibctmtypes.Header { + return chain.CreateTMClientHeader(chain.ChainID, chain.CurrentHeader.Height, clienttypes.Height{}, chain.CurrentHeader.Time, chain.Vals, nil, chain.Signers) +} + +// CreateTMClientHeader creates a TM header to update the TM client. Args are passed in to allow +// caller flexibility to use params that differ from the chain. +func (chain *TestChain) CreateTMClientHeader(chainID string, blockHeight int64, trustedHeight clienttypes.Height, timestamp time.Time, tmValSet, tmTrustedVals *tmtypes.ValidatorSet, signers []tmtypes.PrivValidator) *ibctmtypes.Header { + var ( + valSet *tmproto.ValidatorSet + trustedVals *tmproto.ValidatorSet + ) + require.NotNil(chain.t, tmValSet) + + vsetHash := tmValSet.Hash() + + tmHeader := tmtypes.Header{ + Version: tmprotoversion.Consensus{Block: tmversion.BlockProtocol, App: 2}, + ChainID: chainID, + Height: blockHeight, + Time: timestamp, + LastBlockID: MakeBlockID(make([]byte, tmhash.Size), 10_000, make([]byte, tmhash.Size)), + LastCommitHash: chain.App.LastCommitID().Hash, + DataHash: tmhash.Sum([]byte("data_hash")), + ValidatorsHash: vsetHash, + NextValidatorsHash: vsetHash, + ConsensusHash: tmhash.Sum([]byte("consensus_hash")), + AppHash: chain.CurrentHeader.AppHash, + LastResultsHash: tmhash.Sum([]byte("last_results_hash")), + EvidenceHash: tmhash.Sum([]byte("evidence_hash")), + ProposerAddress: tmValSet.Proposer.Address, //nolint:staticcheck + } + hhash := tmHeader.Hash() + blockID := MakeBlockID(hhash, 3, tmhash.Sum([]byte("part_set"))) + voteSet := tmtypes.NewVoteSet(chainID, blockHeight, 1, tmproto.PrecommitType, tmValSet) + + commit, err := tmtypes.MakeCommit(blockID, blockHeight, 1, voteSet, signers, timestamp) + require.NoError(chain.t, err) + + signedHeader := &tmproto.SignedHeader{ + Header: tmHeader.ToProto(), + Commit: commit.ToProto(), + } + + if tmValSet != nil { + valSet, err = tmValSet.ToProto() + if err != nil { + panic(err) + } + } + + if tmTrustedVals != nil { + trustedVals, err = tmTrustedVals.ToProto() + if err != nil { + panic(err) + } + } + + // The trusted fields may be nil. They may be filled before relaying messages to a client. + // The relayer is responsible for querying client and injecting appropriate trusted fields. + return &ibctmtypes.Header{ + SignedHeader: signedHeader, + ValidatorSet: valSet, + TrustedHeight: trustedHeight, + TrustedValidators: trustedVals, + } +} + +// MakeBlockID copied unimported test functions from tmtypes to use them here +func MakeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) tmtypes.BlockID { + return tmtypes.BlockID{ + Hash: hash, + PartSetHeader: tmtypes.PartSetHeader{ + Total: partSetSize, + Hash: partSetHash, + }, + } +} + +// CreateSortedSignerArray takes two PrivValidators, and the corresponding Validator structs +// (including voting power). It returns a signer array of PrivValidators that matches the +// sorting of ValidatorSet. +// The sorting is first by .VotingPower (descending), with secondary index of .Address (ascending). +func CreateSortedSignerArray(altPrivVal, suitePrivVal tmtypes.PrivValidator, + altVal, suiteVal *tmtypes.Validator) []tmtypes.PrivValidator { + + switch { + case altVal.VotingPower > suiteVal.VotingPower: + return []tmtypes.PrivValidator{altPrivVal, suitePrivVal} + case altVal.VotingPower < suiteVal.VotingPower: + return []tmtypes.PrivValidator{suitePrivVal, altPrivVal} + default: + if bytes.Compare(altVal.Address, suiteVal.Address) == -1 { + return []tmtypes.PrivValidator{altPrivVal, suitePrivVal} + } + return []tmtypes.PrivValidator{suitePrivVal, altPrivVal} + } +} + +// ConnectionOpenInit will construct and execute a MsgConnectionOpenInit. +func (chain *TestChain) ConnectionOpenInit( + counterparty *TestChain, + connection, counterpartyConnection *TestConnection, +) error { + msg := connectiontypes.NewMsgConnectionOpenInit( + connection.ID, connection.ClientID, + counterpartyConnection.ID, connection.CounterpartyClientID, + counterparty.GetPrefix(), DefaultOpenInitVersion, + chain.SenderAccount.GetAddress(), + ) + return chain.sendMsgs(msg) +} + +// ConnectionOpenTry will construct and execute a MsgConnectionOpenTry. +func (chain *TestChain) ConnectionOpenTry( + counterparty *TestChain, + connection, counterpartyConnection *TestConnection, +) error { + counterpartyClient, proofClient := counterparty.QueryClientStateProof(counterpartyConnection.ClientID) + + connectionKey := host.KeyConnection(counterpartyConnection.ID) + proofInit, proofHeight := counterparty.QueryProof(connectionKey) + + proofConsensus, consensusHeight := counterparty.QueryConsensusStateProof(counterpartyConnection.ClientID) + + msg := connectiontypes.NewMsgConnectionOpenTry( + connection.ID, connection.ID, connection.ClientID, // testing doesn't use flexible selection + counterpartyConnection.ID, counterpartyConnection.ClientID, + counterpartyClient, counterparty.GetPrefix(), []*connectiontypes.Version{ConnectionVersion}, + proofInit, proofClient, proofConsensus, + proofHeight, consensusHeight, + chain.SenderAccount.GetAddress(), + ) + return chain.sendMsgs(msg) +} + +// ConnectionOpenAck will construct and execute a MsgConnectionOpenAck. +func (chain *TestChain) ConnectionOpenAck( + counterparty *TestChain, + connection, counterpartyConnection *TestConnection, +) error { + counterpartyClient, proofClient := counterparty.QueryClientStateProof(counterpartyConnection.ClientID) + + connectionKey := host.KeyConnection(counterpartyConnection.ID) + proofTry, proofHeight := counterparty.QueryProof(connectionKey) + + proofConsensus, consensusHeight := counterparty.QueryConsensusStateProof(counterpartyConnection.ClientID) + + msg := connectiontypes.NewMsgConnectionOpenAck( + connection.ID, counterpartyConnection.ID, counterpartyClient, // testing doesn't use flexible selection + proofTry, proofClient, proofConsensus, + proofHeight, consensusHeight, + ConnectionVersion, + chain.SenderAccount.GetAddress(), + ) + return chain.sendMsgs(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.sendMsgs(msg) +} + +// CreatePortCapability binds and claims a capability for the given portID if it does not +// already exist. This function will fail testing on any resulting error. +// NOTE: only creation of a capbility for a transfer or mock port is supported +// Other applications must bind to the port in InitGenesis or modify this code. +func (chain *TestChain) CreatePortCapability(portID string) { + // check if the portId is already binded, if not bind it + _, ok := chain.App.ScopedIBCKeeper.GetCapability(chain.GetContext(), host.PortPath(portID)) + if !ok { + // create capability using the IBC capability keeper + cap, err := chain.App.ScopedIBCKeeper.NewCapability(chain.GetContext(), host.PortPath(portID)) + require.NoError(chain.t, err) + + switch portID { + case MockPort: + // claim capability using the mock capability keeper + err = chain.App.ScopedIBCMockKeeper.ClaimCapability(chain.GetContext(), cap, host.PortPath(portID)) + require.NoError(chain.t, err) + case DNSPort: + // claim capability using the dns capability keeper + err = chain.App.ScopedDNSKeeper.ClaimCapability(chain.GetContext(), cap, host.PortPath(portID)) + require.NoError(chain.t, err) + default: + panic(fmt.Sprintf("unsupported ibc testing package port Channel %s", portID)) + } + } + + chain.App.Commit() + + chain.NextBlock() +} + +// GetPortCapability returns the port capability for the given portID. The capability must +// exist, otherwise testing will fail. +func (chain *TestChain) GetPortCapability(portID string) *capabilitytypes.Capability { + cap, ok := chain.App.ScopedIBCKeeper.GetCapability(chain.GetContext(), host.PortPath(portID)) + require.True(chain.t, ok) + + return cap +} + +// CreateChannelCapability binds and claims a capability for the given portID and channelID +// if it does not already exist. This function will fail testing on any resulting error. +func (chain *TestChain) CreateChannelCapability(portID, channelID string) { + capName := host.ChannelCapabilityPath(portID, channelID) + // check if the portId is already binded, if not bind it + _, ok := chain.App.ScopedIBCKeeper.GetCapability(chain.GetContext(), capName) + if !ok { + cap, err := chain.App.ScopedIBCKeeper.NewCapability(chain.GetContext(), capName) + require.NoError(chain.t, err) + err = chain.App.ScopedTransferKeeper.ClaimCapability(chain.GetContext(), cap, capName) + require.NoError(chain.t, err) + } + + chain.App.Commit() + + chain.NextBlock() +} + +// GetChannelCapability returns the channel capability for the given portID and channelID. +// The capability must exist, otherwise testing will fail. +func (chain *TestChain) GetChannelCapability(portID, channelID string) *capabilitytypes.Capability { + cap, ok := chain.App.ScopedIBCKeeper.GetCapability(chain.GetContext(), host.ChannelCapabilityPath(portID, channelID)) + require.True(chain.t, ok) + + return cap +} + +// ChanOpenInit will construct and execute a MsgChannelOpenInit. +func (chain *TestChain) ChanOpenInit( + ch, counterparty TestChannel, + order channeltypes.Order, + connectionID string, +) error { + msg := channeltypes.NewMsgChannelOpenInit( + ch.Port, ch.Channel, + ch.Version, order, []string{connectionID}, + counterparty.Port, counterparty.Channel, + chain.SenderAccount.GetAddress(), + ) + return chain.sendMsgs(msg) +} + +// ChanOpenTry will construct and execute a MsgChannelOpenTry. +func (chain *TestChain) ChanOpenTry( + counterparty *TestChain, + ch, counterpartyCh TestChannel, + order channeltypes.Order, + connectionID string, +) error { + proof, height := counterparty.QueryProof(host.KeyChannel(counterpartyCh.Port, counterpartyCh.Channel)) + + msg := channeltypes.NewMsgChannelOpenTry( + ch.Port, ch.Channel, ch.Channel, // testing doesn't use flexible selection + ch.Version, order, []string{connectionID}, + counterpartyCh.Port, counterpartyCh.Channel, + counterpartyCh.Version, + proof, height, + chain.SenderAccount.GetAddress(), + ) + return chain.sendMsgs(msg) +} + +// ChanOpenAck will construct and execute a MsgChannelOpenAck. +func (chain *TestChain) ChanOpenAck( + counterparty *TestChain, + ch, counterpartyCh TestChannel, +) error { + proof, height := counterparty.QueryProof(host.KeyChannel(counterpartyCh.Port, counterpartyCh.Channel)) + + msg := channeltypes.NewMsgChannelOpenAck( + ch.Port, ch.Channel, + counterpartyCh.Channel, counterpartyCh.Version, // testing doesn't use flexible selection + proof, height, + chain.SenderAccount.GetAddress(), + ) + return chain.sendMsgs(msg) +} + +// ChanOpenConfirm will construct and execute a MsgChannelOpenConfirm. +func (chain *TestChain) ChanOpenConfirm( + counterparty *TestChain, + ch, counterpartyCh TestChannel, +) error { + proof, height := counterparty.QueryProof(host.KeyChannel(counterpartyCh.Port, counterpartyCh.Channel)) + + msg := channeltypes.NewMsgChannelOpenConfirm( + ch.Port, ch.Channel, + proof, height, + chain.SenderAccount.GetAddress(), + ) + return chain.sendMsgs(msg) +} + +// ChanCloseInit will construct and execute a MsgChannelCloseInit. +// +// NOTE: does not work with ibc-transfer module +func (chain *TestChain) ChanCloseInit( + counterparty *TestChain, + channel TestChannel, +) error { + msg := channeltypes.NewMsgChannelCloseInit( + channel.Port, channel.Channel, + chain.SenderAccount.GetAddress(), + ) + return chain.sendMsgs(msg) +} + +// GetPacketData returns a ibc-transfer marshalled packet to be used for +// callback testing. +func (chain *TestChain) GetPacketData(counterparty *TestChain) []byte { + packet := ibctransfertypes.FungibleTokenPacketData{ + Denom: TestCoin.Denom, + Amount: TestCoin.Amount.Uint64(), + Sender: chain.SenderAccount.GetAddress().String(), + Receiver: counterparty.SenderAccount.GetAddress().String(), + } + + return packet.GetBytes() +} + +// SendPacket simulates sending a packet through the channel keeper. No message needs to be +// passed since this call is made from a module. +func (chain *TestChain) SendPacket( + packet exported.PacketI, +) error { + channelCap := chain.GetChannelCapability(packet.GetSourcePort(), packet.GetSourceChannel()) + + // no need to send message, acting as a module + err := chain.App.IBCKeeper.ChannelKeeper.SendPacket(chain.GetContext(), channelCap, packet) + if err != nil { + return err + } + + // commit changes + chain.App.Commit() + chain.NextBlock() + + return nil +} + +// WriteReceipt simulates receiving and writing a receipt to the chain. +func (chain *TestChain) WriteReceipt( + packet exported.PacketI, +) error { + channelCap := chain.GetChannelCapability(packet.GetDestPort(), packet.GetDestChannel()) + + // no need to send message, acting as a handler + err := chain.App.IBCKeeper.ChannelKeeper.WriteReceipt(chain.GetContext(), channelCap, packet) + if err != nil { + return err + } + + // commit changes + chain.App.Commit() + chain.NextBlock() + + return nil +} + +// WriteAcknowledgement simulates writing an acknowledgement to the chain. +func (chain *TestChain) WriteAcknowledgement( + packet exported.PacketI, +) error { + // no need to send message, acting as a handler + err := chain.App.IBCKeeper.ChannelKeeper.WriteAcknowledgement(chain.GetContext(), packet, TestHash) + if err != nil { + return err + } + + // commit changes + chain.App.Commit() + chain.NextBlock() + + return nil +} + +// AcknowledgementExecuted simulates deleting a packet commitment with the +// given packet sequence. +func (chain *TestChain) AcknowledgementExecuted( + packet exported.PacketI, +) error { + channelCap := chain.GetChannelCapability(packet.GetSourcePort(), packet.GetSourceChannel()) + + // no need to send message, acting as a handler + err := chain.App.IBCKeeper.ChannelKeeper.AcknowledgementExecuted(chain.GetContext(), channelCap, packet) + if err != nil { + return err + } + + // commit changes + chain.App.Commit() + chain.NextBlock() + + return nil +} diff --git a/x/ibc-dns/testing/coordinator.go b/x/ibc-dns/testing/coordinator.go new file mode 100644 index 0000000..f7bef57 --- /dev/null +++ b/x/ibc-dns/testing/coordinator.go @@ -0,0 +1,673 @@ +package ibctesting + +import ( + "fmt" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + channeltypes "github.com/cosmos/cosmos-sdk/x/ibc/core/04-channel/types" + host "github.com/cosmos/cosmos-sdk/x/ibc/core/24-host" + "github.com/cosmos/cosmos-sdk/x/ibc/core/exported" +) + +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, chain ...*TestChain) *Coordinator { + chains := make(map[string]*TestChain) + + for _, c := range chain { + chains[c.ChainID] = c + } + return &Coordinator{ + t: t, + Chains: chains, + } +} + +// Setup constructs a TM client, connection, and channel on both chains provided. It will +// fail if any error occurs. The clientID's, TestConnections, and TestChannels are returned +// for both chains. The channels created are connected to the ibc-transfer application. +func (coord *Coordinator) Setup( + chainA, chainB *TestChain, + srcClientID, dstClientID string, + order channeltypes.Order, +) (string, string, *TestConnection, *TestConnection, TestChannel, TestChannel) { + clientA, clientB, connA, connB := coord.SetupClientConnections(chainA, chainB, srcClientID, dstClientID, Tendermint) + + // channels can also be referenced through the returned connections + channelA, channelB := coord.CreateMockChannels(chainA, chainB, connA, connB, order) + + 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, + srcClientID, dstClientID string, + clientType string, +) (string, string) { + require.NoError(coord.t, coord.CreateClient(chainA, chainB, srcClientID, clientType)) + require.NoError(coord.t, coord.CreateClient(chainB, chainA, dstClientID, clientType)) + + return srcClientID, dstClientID +} + +// 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( + srcChain, dstChain *TestChain, + srcClientID, dstClientID string, + clientType string, +) (string, string, *TestConnection, *TestConnection) { + + clientA, clientB := coord.SetupClients(srcChain, dstChain, srcClientID, dstClientID, clientType) + + connA, connB := coord.CreateConnection(srcChain, dstChain, 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, + clientID string, + clientType string, +) (err error) { + coord.CommitBlock(source, counterparty) + + switch clientType { + case 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 nil +} + +// UpdateClient updates a counterparty client on the source chain. +func (coord *Coordinator) UpdateClient( + source, counterparty *TestChain, + clientID string, + clientType string, +) (err error) { + coord.CommitBlock(source, counterparty) + + switch clientType { + case 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 +} + +// CreateMockChannels constructs and executes channel handshake messages to create OPEN +// channels that use a mock application module that returns nil on all callbacks. This +// function is expects the channels to be successfully opened otherwise testing will +// fail. +func (coord *Coordinator) CreateMockChannels( + chainA, chainB *TestChain, + connA, connB *TestConnection, + order channeltypes.Order, +) (TestChannel, TestChannel) { + return coord.CreateChannel(chainA, chainB, connA, connB, MockPort, MockPort, order) +} + +// CreateDNSChannels constructs and executes channel handshake messages to create OPEN +// ibc-dns channels on chainA and chainB. The function expects the channels to be +// successfully opened otherwise testing will fail. +func (coord *Coordinator) CreateDNSChannels( + chainA, chainB *TestChain, + connA, connB *TestConnection, + order channeltypes.Order, +) (TestChannel, TestChannel) { + return coord.CreateChannel(chainA, chainB, connA, connB, DNSPort, DNSPort, order) +} + +// 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, + sourcePortID, counterpartyPortID string, + order channeltypes.Order, +) (TestChannel, TestChannel) { + + channelA, channelB, err := coord.ChanOpenInit(chainA, chainB, connA, connB, sourcePortID, counterpartyPortID, 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 exported.PacketI, + counterpartyClientID string, +) error { + if err := source.SendPacket(packet); err != nil { + return err + } + coord.IncrementTime() + + // update source client on counterparty connection + return coord.UpdateClient( + counterparty, source, + counterpartyClientID, Tendermint, + ) +} + +// RecvPacket receives a channel packet on the counterparty chain and updates +// the client on the source chain representing the counterparty. +func (coord *Coordinator) RecvPacket( + source, counterparty *TestChain, + sourceClient string, + packet channeltypes.Packet, +) error { + // get proof of packet commitment on source + packetKey := host.KeyPacketCommitment(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + proof, proofHeight := source.QueryProof(packetKey) + + recvMsg := channeltypes.NewMsgRecvPacket(packet, proof, proofHeight, counterparty.SenderAccount.GetAddress()) + + // receive on counterparty and update source client + return coord.SendMsgs(counterparty, source, sourceClient, []sdk.Msg{recvMsg}) +} + +// WriteReceipt receives a packet through the channel keeper on the source chain, writes a receipt, and updates the +// counterparty client for the source chain. +func (coord *Coordinator) WriteReceipt( + source, counterparty *TestChain, + packet exported.PacketI, + counterpartyClientID string, +) error { + if err := source.WriteReceipt(packet); err != nil { + return err + } + coord.IncrementTime() + + // update source client on counterparty connection + return coord.UpdateClient( + counterparty, source, + counterpartyClientID, Tendermint, + ) +} + +// WriteAcknowledgement writes an acknowledgement to the channel keeper on the source chain and updates the +// counterparty client for the source chain. +func (coord *Coordinator) WriteAcknowledgement( + source, counterparty *TestChain, + packet exported.PacketI, + counterpartyClientID string, +) error { + if err := source.WriteAcknowledgement(packet); err != nil { + return err + } + coord.IncrementTime() + + // update source client on counterparty connection + return coord.UpdateClient( + counterparty, source, + counterpartyClientID, Tendermint, + ) +} + +// AcknowledgePacket acknowledges on the source chain the packet received on +// the counterparty chain and updates the client on the counterparty representing +// the source chain. +// TODO: add a query for the acknowledgement by events +// - https://github.com/cosmos/cosmos-sdk/issues/6509 +func (coord *Coordinator) AcknowledgePacket( + source, counterparty *TestChain, + counterpartyClient string, + packet channeltypes.Packet, ack []byte, +) error { + // get proof of acknowledgement on counterparty + packetKey := host.KeyPacketAcknowledgement(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + proof, proofHeight := counterparty.QueryProof(packetKey) + + ackMsg := channeltypes.NewMsgAcknowledgement(packet, ack, proof, proofHeight, source.SenderAccount.GetAddress()) + return coord.SendMsgs(source, counterparty, counterpartyClient, []sdk.Msg{ackMsg}) +} + +// AcknowledgementExecuted deletes the packet commitment with the given +// packet sequence since the acknowledgement has been verified. +func (coord *Coordinator) AcknowledgementExecuted( + source, counterparty *TestChain, + packet exported.PacketI, + counterpartyClientID string, +) error { + if err := source.AcknowledgementExecuted(packet); err != nil { + return err + } + coord.IncrementTime() + + // update source client on counterparty connection + return coord.UpdateClient( + counterparty, source, + counterpartyClientID, Tendermint, + ) +} + +// RelayPacket receives a channel packet on counterparty, queries the ack +// and acknowledges the packet on source. The clients are updated as needed. +func (coord *Coordinator) RelayPacket( + source, counterparty *TestChain, + sourceClient, counterpartyClient string, + packet channeltypes.Packet, ack []byte, +) error { + if err := coord.RecvPacket(source, counterparty, sourceClient, packet); err != nil { + return err + } + + return coord.AcknowledgePacket(source, counterparty, counterpartyClient, packet, ack) +} + +// 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}) + } +} + +// SendMsg delivers a single provided message to the chain. The counterparty +// client is update with the new source consensus state. +func (coord *Coordinator) SendMsg(source, counterparty *TestChain, counterpartyClientID string, msg sdk.Msg) error { + return coord.SendMsgs(source, counterparty, counterpartyClientID, []sdk.Msg{msg}) +} + +// SendMsgs delivers the provided messages to the chain. The counterparty +// client is updated with the new source consensus state. +func (coord *Coordinator) SendMsgs(source, counterparty *TestChain, counterpartyClientID string, msgs []sdk.Msg) error { + if err := source.sendMsgs(msgs...); err != nil { + return err + } + + coord.IncrementTime() + + // update source client on counterparty connection + return coord.UpdateClient( + counterparty, source, + counterpartyClientID, Tendermint, + ) +} + +// 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.AddTestConnection(clientID, counterpartyClientID) + counterpartyConnection := counterparty.AddTestConnection(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, 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 + return coord.UpdateClient( + counterparty, source, + counterpartyConnection.ClientID, Tendermint, + ) +} + +// 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 + return coord.UpdateClient( + counterparty, source, + counterpartyConnection.ClientID, Tendermint, + ) +} + +// 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 + return coord.UpdateClient( + counterparty, source, + counterpartyConnection.ClientID, Tendermint, + ) +} + +// 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, + sourcePortID, counterpartyPortID string, + order channeltypes.Order, +) (TestChannel, TestChannel, error) { + sourceChannel := connection.AddTestChannel(sourcePortID) + counterpartyChannel := counterpartyConnection.AddTestChannel(counterpartyPortID) + + // NOTE: only creation of a capability for a transfer or mock port is supported + // Other applications must bind to the port in InitGenesis or modify this code. + source.CreatePortCapability(sourceChannel.Port) + 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, Tendermint, + ); err != nil { + return sourceChannel, counterpartyChannel, err + } + + return sourceChannel, counterpartyChannel, nil +} + +// ChanOpenInitOnBothChains initializes a channel on the source chain and counterparty chain +// with the state INIT using the OpenInit handshake call. +func (coord *Coordinator) ChanOpenInitOnBothChains( + source, counterparty *TestChain, + connection, counterpartyConnection *TestConnection, + sourcePortID, counterpartyPortID string, + order channeltypes.Order, +) (TestChannel, TestChannel, error) { + sourceChannel := connection.AddTestChannel(sourcePortID) + counterpartyChannel := counterpartyConnection.AddTestChannel(counterpartyPortID) + + // NOTE: only creation of a capability for a transfer or mock port is supported + // Other applications must bind to the port in InitGenesis or modify this code. + source.CreatePortCapability(sourceChannel.Port) + counterparty.CreatePortCapability(counterpartyChannel.Port) + coord.IncrementTime() + + // initialize channel on source + if err := source.ChanOpenInit(sourceChannel, counterpartyChannel, order, connection.ID); err != nil { + return sourceChannel, counterpartyChannel, err + } + coord.IncrementTime() + + // initialize channel on counterparty + if err := counterparty.ChanOpenInit(counterpartyChannel, sourceChannel, order, counterpartyConnection.ID); err != nil { + return sourceChannel, counterpartyChannel, err + } + coord.IncrementTime() + + // update counterparty client on source connection + if err := coord.UpdateClient( + source, counterparty, + connection.ClientID, Tendermint, + ); err != nil { + return sourceChannel, counterpartyChannel, err + } + + // update source client on counterparty connection + if err := coord.UpdateClient( + counterparty, source, + counterpartyConnection.ClientID, 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 { + source.CreatePortCapability(sourceChannel.Port) + coord.IncrementTime() + + // 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 + return coord.UpdateClient( + counterparty, source, + connection.CounterpartyClientID, Tendermint, + ) +} + +// 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 + return coord.UpdateClient( + counterparty, source, + sourceChannel.CounterpartyClientID, Tendermint, + ) +} + +// 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 + return coord.UpdateClient( + counterparty, source, + sourceChannel.CounterpartyClientID, Tendermint, + ) +} + +// 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 + return coord.UpdateClient( + counterparty, source, + channel.CounterpartyClientID, Tendermint, + ) +} + +// 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.Port, testChannel.Channel, channel) + + coord.CommitBlock(source) + + // update source client on counterparty connection + return coord.UpdateClient( + counterparty, source, + testChannel.CounterpartyClientID, Tendermint, + ) +} diff --git a/x/ibc-dns/testing/solomachine.go b/x/ibc-dns/testing/solomachine.go new file mode 100644 index 0000000..21b0251 --- /dev/null +++ b/x/ibc-dns/testing/solomachine.go @@ -0,0 +1,317 @@ +package ibctesting + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto" + + "github.com/cosmos/cosmos-sdk/codec" + kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/cosmos/cosmos-sdk/crypto/types/multisig" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/cosmos/cosmos-sdk/x/auth/tx" + clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/core/02-client/types" + commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/core/23-commitment/types" + host "github.com/cosmos/cosmos-sdk/x/ibc/core/24-host" + "github.com/cosmos/cosmos-sdk/x/ibc/core/exported" + solomachinetypes "github.com/cosmos/cosmos-sdk/x/ibc/light-clients/06-solomachine/types" +) + +var prefix = commitmenttypes.NewMerklePrefix([]byte("ibc")) + +// Solomachine is a testing helper used to simulate a counterparty +// solo machine client. +type Solomachine struct { + t *testing.T + + cdc codec.BinaryMarshaler + ClientID string + PrivateKeys []crypto.PrivKey // keys used for signing + PublicKeys []crypto.PubKey // keys used for generating solo machine pub key + PublicKey crypto.PubKey // key used for verification + Sequence uint64 + Time uint64 + Diversifier string +} + +// NewSolomachine returns a new solomachine instance with an `nKeys` amount of +// generated private/public key pairs and a sequence starting at 1. If nKeys +// is greater than 1 then a multisig public key is used. +func NewSolomachine(t *testing.T, cdc codec.BinaryMarshaler, clientID, diversifier string, nKeys uint64) *Solomachine { + privKeys, pubKeys, pk := GenerateKeys(t, nKeys) + + return &Solomachine{ + t: t, + cdc: cdc, + ClientID: clientID, + PrivateKeys: privKeys, + PublicKeys: pubKeys, + PublicKey: pk, + Sequence: 1, + Time: 10, + Diversifier: diversifier, + } +} + +// GenerateKeys generates a new set of secp256k1 private keys and public keys. +// If the number of keys is greater than one then the public key returned represents +// a multisig public key. The private keys are used for signing, the public +// keys are used for generating the public key and the public key is used for +// solo machine verification. The usage of secp256k1 is entirely arbitrary. +// The key type can be swapped for any key type supported by the PublicKey +// interface, if needed. The same is true for the amino based Multisignature +// public key. +func GenerateKeys(t *testing.T, n uint64) ([]crypto.PrivKey, []crypto.PubKey, crypto.PubKey) { + require.NotEqual(t, uint64(0), n, "generation of zero keys is not allowed") + + privKeys := make([]crypto.PrivKey, n) + pubKeys := make([]crypto.PubKey, n) + for i := uint64(0); i < n; i++ { + privKeys[i] = secp256k1.GenPrivKey() + pubKeys[i] = privKeys[i].PubKey() + } + + var pk crypto.PubKey + if len(privKeys) > 1 { + // generate multi sig pk + pk = kmultisig.NewLegacyAminoPubKey(int(n), pubKeys) + } else { + pk = privKeys[0].PubKey() + } + + return privKeys, pubKeys, pk +} + +// ClientState returns a new solo machine ClientState instance. Default usage does not allow update +// after governance proposal +func (solo *Solomachine) ClientState() *solomachinetypes.ClientState { + return solomachinetypes.NewClientState(solo.Sequence, solo.ConsensusState(), false) +} + +// ConsensusState returns a new solo machine ConsensusState instance +func (solo *Solomachine) ConsensusState() *solomachinetypes.ConsensusState { + publicKey, err := tx.PubKeyToAny(solo.PublicKey) + require.NoError(solo.t, err) + + return &solomachinetypes.ConsensusState{ + PublicKey: publicKey, + Diversifier: solo.Diversifier, + Timestamp: solo.Time, + } +} + +// GetHeight returns an exported.Height with Sequence as VersionHeight +func (solo *Solomachine) GetHeight() exported.Height { + return clienttypes.NewHeight(0, solo.Sequence) +} + +// CreateHeader generates a new private/public key pair and creates the +// necessary signature to construct a valid solo machine header. +func (solo *Solomachine) CreateHeader() *solomachinetypes.Header { + // generate new private keys and signature for header + newPrivKeys, newPubKeys, newPubKey := GenerateKeys(solo.t, uint64(len(solo.PrivateKeys))) + + publicKey, err := tx.PubKeyToAny(newPubKey) + require.NoError(solo.t, err) + + data := &solomachinetypes.HeaderData{ + NewPubKey: publicKey, + NewDiversifier: solo.Diversifier, + } + + dataBz, err := solo.cdc.MarshalBinaryBare(data) + require.NoError(solo.t, err) + + signBytes := &solomachinetypes.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + DataType: solomachinetypes.HEADER, + Data: dataBz, + } + + bz, err := solo.cdc.MarshalBinaryBare(signBytes) + require.NoError(solo.t, err) + + sig := solo.GenerateSignature(bz) + + header := &solomachinetypes.Header{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Signature: sig, + NewPublicKey: publicKey, + NewDiversifier: solo.Diversifier, + } + + // assumes successful header update + solo.Sequence++ + solo.PrivateKeys = newPrivKeys + solo.PublicKeys = newPubKeys + solo.PublicKey = newPubKey + + return header +} + +// CreateMisbehaviour constructs testing misbehaviour for the solo machine client +// by signing over two different data bytes at the same sequence. +func (solo *Solomachine) CreateMisbehaviour() *solomachinetypes.Misbehaviour { + path := solo.GetClientStatePath("counterparty") + dataOne, err := solomachinetypes.ClientStateDataBytes(solo.cdc, path, solo.ClientState()) + require.NoError(solo.t, err) + + path = solo.GetConsensusStatePath("counterparty", clienttypes.NewHeight(0, 1)) + dataTwo, err := solomachinetypes.ConsensusStateDataBytes(solo.cdc, path, solo.ConsensusState()) + require.NoError(solo.t, err) + + signBytes := &solomachinetypes.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + DataType: solomachinetypes.CLIENT, + Data: dataOne, + } + + bz, err := solo.cdc.MarshalBinaryBare(signBytes) + require.NoError(solo.t, err) + + sig := solo.GenerateSignature(bz) + signatureOne := solomachinetypes.SignatureAndData{ + Signature: sig, + DataType: solomachinetypes.CLIENT, + Data: dataOne, + Timestamp: solo.Time, + } + + // misbehaviour signaturess can have different timestamps + solo.Time++ + + signBytes = &solomachinetypes.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + DataType: solomachinetypes.CONSENSUS, + Data: dataTwo, + } + + bz, err = solo.cdc.MarshalBinaryBare(signBytes) + require.NoError(solo.t, err) + + sig = solo.GenerateSignature(bz) + signatureTwo := solomachinetypes.SignatureAndData{ + Signature: sig, + DataType: solomachinetypes.CONSENSUS, + Data: dataTwo, + Timestamp: solo.Time, + } + + return &solomachinetypes.Misbehaviour{ + ClientId: solo.ClientID, + Sequence: solo.Sequence, + SignatureOne: &signatureOne, + SignatureTwo: &signatureTwo, + } +} + +// GenerateSignature uses the stored private keys to generate a signature +// over the sign bytes with each key. If the amount of keys is greater than +// 1 then a multisig data type is returned. +func (solo *Solomachine) GenerateSignature(signBytes []byte) []byte { + sigs := make([]signing.SignatureData, len(solo.PrivateKeys)) + for i, key := range solo.PrivateKeys { + sig, err := key.Sign(signBytes) + require.NoError(solo.t, err) + + sigs[i] = &signing.SingleSignatureData{ + Signature: sig, + } + } + + var sigData signing.SignatureData + if len(sigs) == 1 { + // single public key + sigData = sigs[0] + } else { + // generate multi signature data + multiSigData := multisig.NewMultisig(len(sigs)) + for i, sig := range sigs { + multisig.AddSignature(multiSigData, sig, i) + } + + sigData = multiSigData + } + + protoSigData := signing.SignatureDataToProto(sigData) + bz, err := solo.cdc.MarshalBinaryBare(protoSigData) + require.NoError(solo.t, err) + + return bz +} + +// GetClientStatePath returns the commitment path for the client state. +func (solo *Solomachine) GetClientStatePath(counterpartyClientIdentifier string) commitmenttypes.MerklePath { + clientPrefixedPath := "clients/" + counterpartyClientIdentifier + "/" + host.ClientStatePath() + path, err := commitmenttypes.ApplyPrefix(prefix, clientPrefixedPath) + require.NoError(solo.t, err) + + return path +} + +// GetConsensusStatePath returns the commitment path for the consensus state. +func (solo *Solomachine) GetConsensusStatePath(counterpartyClientIdentifier string, consensusHeight exported.Height) commitmenttypes.MerklePath { + clientPrefixedPath := "clients/" + counterpartyClientIdentifier + "/" + host.ConsensusStatePath(consensusHeight) + path, err := commitmenttypes.ApplyPrefix(prefix, clientPrefixedPath) + require.NoError(solo.t, err) + + return path +} + +// GetConnectionStatePath returns the commitment path for the connection state. +func (solo *Solomachine) GetConnectionStatePath(connID string) commitmenttypes.MerklePath { + path, err := commitmenttypes.ApplyPrefix(prefix, host.ConnectionPath(connID)) + require.NoError(solo.t, err) + + return path +} + +// GetChannelStatePath returns the commitment path for that channel state. +func (solo *Solomachine) GetChannelStatePath(portID, channelID string) commitmenttypes.MerklePath { + path, err := commitmenttypes.ApplyPrefix(prefix, host.ChannelPath(portID, channelID)) + require.NoError(solo.t, err) + + return path +} + +// GetPacketCommitmentPath returns the commitment path for a packet commitment. +func (solo *Solomachine) GetPacketCommitmentPath(portID, channelID string) commitmenttypes.MerklePath { + path, err := commitmenttypes.ApplyPrefix(prefix, host.PacketCommitmentPath(portID, channelID, solo.Sequence)) + require.NoError(solo.t, err) + + return path +} + +// GetPacketAcknowledgementPath returns the commitment path for a packet acknowledgement. +func (solo *Solomachine) GetPacketAcknowledgementPath(portID, channelID string) commitmenttypes.MerklePath { + path, err := commitmenttypes.ApplyPrefix(prefix, host.PacketAcknowledgementPath(portID, channelID, solo.Sequence)) + require.NoError(solo.t, err) + + return path +} + +// GetPacketReceiptPath returns the commitment path for a packet receipt +// and an absent receipts. +func (solo *Solomachine) GetPacketReceiptPath(portID, channelID string) commitmenttypes.MerklePath { + path, err := commitmenttypes.ApplyPrefix(prefix, host.PacketReceiptPath(portID, channelID, solo.Sequence)) + require.NoError(solo.t, err) + + return path +} + +// GetNextSequenceRecvPath returns the commitment path for the next sequence recv counter. +func (solo *Solomachine) GetNextSequenceRecvPath(portID, channelID string) commitmenttypes.MerklePath { + path, err := commitmenttypes.ApplyPrefix(prefix, host.NextSequenceRecvPath(portID, channelID)) + require.NoError(solo.t, err) + + return path +} diff --git a/x/ibc-dns/testing/types.go b/x/ibc-dns/testing/types.go new file mode 100644 index 0000000..3bdb277 --- /dev/null +++ b/x/ibc-dns/testing/types.go @@ -0,0 +1,65 @@ +package ibctesting + +import ( + "fmt" +) + +// TestConnection is a testing helper struct to keep track of the connectionID, source clientID, +// counterparty clientID, and the next channel version used in creating and interacting with a +// connection. +type TestConnection struct { + ID string + ClientID string + CounterpartyClientID string + NextChannelVersion string + Channels []TestChannel +} + +// AddTestChannel appends a new TestChannel which contains references to the port and channel ID +// used for channel creation and interaction. See 'NextTestChannel' for channel ID naming format. +func (conn *TestConnection) AddTestChannel(portID string) TestChannel { + channel := conn.NextTestChannel(portID) + conn.Channels = append(conn.Channels, channel) + return channel +} + +// NextTestChannel returns the next test channel to be created on this connection, but does not +// add it to the list of created channels. This function is expected to be used when the caller +// has not created the associated channel in app state, but would still like to refer to the +// non-existent channel usually to test for its non-existence. +// +// channel ID format: -chan +// +// The port is passed in by the caller. +func (conn *TestConnection) NextTestChannel(portID string) TestChannel { + channelID := fmt.Sprintf("%s-%s%d", conn.ID, ChannelIDPrefix, len(conn.Channels)) + return TestChannel{ + Port: portID, + Channel: channelID, + ClientID: conn.ClientID, + CounterpartyClientID: conn.CounterpartyClientID, + Version: conn.NextChannelVersion, + } +} + +// FirstOrNextTestChannel returns the first test channel if it exists, otherwise it +// returns the next test channel to be created. This function is expected to be used +// when the caller does not know if the channel has or has not been created in app +// state, but would still like to refer to it to test existence or non-existence. +func (conn *TestConnection) FirstOrNextTestChannel(portID string) TestChannel { + if len(conn.Channels) > 0 { + return conn.Channels[0] + } + return conn.NextTestChannel(portID) +} + +// TestChannel is a testing helper struct to keep track of the portID and channelID +// used in creating and interacting with a channel. The clientID and counterparty +// client Channel are also tracked to cut down on querying and argument passing. +type TestChannel struct { + Port string + Channel string + ClientID string + CounterpartyClientID string + Version string +}