diff --git a/x/ibc/04-channel/alias.go b/x/ibc/04-channel/alias.go index ee12c61e2..697f2338e 100644 --- a/x/ibc/04-channel/alias.go +++ b/x/ibc/04-channel/alias.go @@ -28,6 +28,7 @@ var ( QuerierConnectionChannels = keeper.QuerierConnectionChannels NewChannel = types.NewChannel NewCounterparty = types.NewCounterparty + NewIdentifiedChannel = types.NewIdentifiedChannel RegisterCodec = types.RegisterCodec ErrChannelExists = types.ErrChannelExists ErrChannelNotFound = types.ErrChannelNotFound @@ -50,7 +51,11 @@ var ( NewMsgTimeout = types.NewMsgTimeout NewMsgAcknowledgement = types.NewMsgAcknowledgement NewPacket = types.NewPacket + NewPacketAckCommitment = types.NewPacketAckCommitment + NewPacketSequence = types.NewPacketSequence NewChannelResponse = types.NewChannelResponse + DefaultGenesisState = types.DefaultGenesisState + NewGenesisState = types.NewGenesisState // variable aliases SubModuleCdc = types.SubModuleCdc @@ -68,6 +73,7 @@ type ( Keeper = keeper.Keeper Channel = types.Channel Counterparty = types.Counterparty + IdentifiedChannel = types.IdentifiedChannel ClientKeeper = types.ClientKeeper ConnectionKeeper = types.ConnectionKeeper PortKeeper = types.PortKeeper @@ -82,4 +88,7 @@ type ( MsgTimeout = types.MsgTimeout Packet = types.Packet ChannelResponse = types.ChannelResponse + PacketAckCommitment = types.PacketAckCommitment + PacketSequence = types.PacketSequence + GenesisState = types.GenesisState ) diff --git a/x/ibc/04-channel/client/utils/utils.go b/x/ibc/04-channel/client/utils/utils.go index 5729e50c1..9886a79c0 100644 --- a/x/ibc/04-channel/client/utils/utils.go +++ b/x/ibc/04-channel/client/utils/utils.go @@ -29,8 +29,8 @@ func QueryPacket( return types.PacketResponse{}, err } - destPortID := channelRes.Channel.Channel.Counterparty.PortID - destChannelID := channelRes.Channel.Channel.Counterparty.ChannelID + destPortID := channelRes.Channel.Counterparty.PortID + destChannelID := channelRes.Channel.Counterparty.ChannelID packet := types.NewPacket( res.Value, diff --git a/x/ibc/04-channel/genesis.go b/x/ibc/04-channel/genesis.go new file mode 100644 index 000000000..c44767a40 --- /dev/null +++ b/x/ibc/04-channel/genesis.go @@ -0,0 +1,37 @@ +package channel + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// InitGenesis initializes the ibc channel submodule's state from a provided genesis +// state. +func InitGenesis(ctx sdk.Context, k Keeper, gs GenesisState) { + for _, channel := range gs.Channels { + ch := NewChannel(channel.State, channel.Ordering, channel.Counterparty, channel.ConnectionHops, channel.Version) + k.SetChannel(ctx, channel.PortID, channel.ID, ch) + } + for _, ack := range gs.Acknowledgements { + k.SetPacketAcknowledgement(ctx, ack.PortID, ack.ChannelID, ack.Sequence, ack.Hash) + } + for _, commitment := range gs.Commitments { + k.SetPacketCommitment(ctx, commitment.PortID, commitment.ChannelID, commitment.Sequence, commitment.Hash) + } + for _, ss := range gs.SendSequences { + k.SetNextSequenceSend(ctx, ss.PortID, ss.ChannelID, ss.Sequence) + } + for _, rs := range gs.RecvSequences { + k.SetNextSequenceRecv(ctx, rs.PortID, rs.ChannelID, rs.Sequence) + } +} + +// ExportGenesis returns the ibc channel submodule's exported genesis. +func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState { + return GenesisState{ + Channels: k.GetAllChannels(ctx), + Acknowledgements: k.GetAllPacketAcks(ctx), + Commitments: k.GetAllPacketCommitments(ctx), + SendSequences: k.GetAllPacketSendSeqs(ctx), + RecvSequences: k.GetAllPacketRecvSeqs(ctx), + } +} diff --git a/x/ibc/04-channel/keeper/handshake.go b/x/ibc/04-channel/keeper/handshake.go index c9074f975..1accf5f7d 100644 --- a/x/ibc/04-channel/keeper/handshake.go +++ b/x/ibc/04-channel/keeper/handshake.go @@ -202,9 +202,9 @@ func (k Keeper) ChanOpenAck( } // counterparty of the counterparty channel end (i.e self) - counterparty := types.NewCounterparty(portID, channelID) + expectedCounterparty := types.NewCounterparty(portID, channelID) expectedChannel := types.NewChannel( - exported.TRYOPEN, channel.Ordering, counterparty, + exported.TRYOPEN, channel.Ordering, expectedCounterparty, counterpartyHops, counterpartyVersion, ) diff --git a/x/ibc/04-channel/keeper/keeper.go b/x/ibc/04-channel/keeper/keeper.go index f7b97804e..dd2e04f0a 100644 --- a/x/ibc/04-channel/keeper/keeper.go +++ b/x/ibc/04-channel/keeper/keeper.go @@ -3,8 +3,11 @@ package keeper import ( "encoding/binary" "fmt" + "strconv" + "strings" "github.com/tendermint/tendermint/libs/log" + db "github.com/tendermint/tm-db" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -134,6 +137,90 @@ func (k Keeper) GetPacketAcknowledgement(ctx sdk.Context, portID, channelID stri return bz, true } +// IteratePacketSequence provides an iterator over all send and receive sequences. For each +// sequence, cb will be called. If the cb returns true, the iterator will close +// and stop. +func (k Keeper) IteratePacketSequence(ctx sdk.Context, send bool, cb func(portID, channelID string, sequence uint64) bool) { + store := ctx.KVStore(k.storeKey) + var iterator db.Iterator + if send { + iterator = sdk.KVStorePrefixIterator(store, []byte(ibctypes.KeyNextSeqSendPrefix)) + } else { + iterator = sdk.KVStorePrefixIterator(store, []byte(ibctypes.KeyNextSeqRecvPrefix)) + } + + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + keySplit := strings.Split(string(iterator.Key()), "/") + portID := keySplit[2] + channelID := keySplit[4] + + sequence := sdk.BigEndianToUint64(iterator.Value()) + + if cb(portID, channelID, sequence) { + break + } + } +} + +// GetAllPacketSendSeqs returns all stored next send sequences. +func (k Keeper) GetAllPacketSendSeqs(ctx sdk.Context) (seqs []types.PacketSequence) { + k.IteratePacketSequence(ctx, true, func(portID, channelID string, nextSendSeq uint64) bool { + ps := types.NewPacketSequence(portID, channelID, nextSendSeq) + seqs = append(seqs, ps) + return false + }) + return seqs +} + +// GetAllPacketRecvSeqs returns all stored next recv sequences. +func (k Keeper) GetAllPacketRecvSeqs(ctx sdk.Context) (seqs []types.PacketSequence) { + k.IteratePacketSequence(ctx, false, func(portID, channelID string, nextRecvSeq uint64) bool { + ps := types.NewPacketSequence(portID, channelID, nextRecvSeq) + seqs = append(seqs, ps) + return false + }) + return seqs +} + +// IteratePacketCommitment provides an iterator over all PacketCommitment objects. For each +// aknowledgement, cb will be called. If the cb returns true, the iterator will close +// and stop. +func (k Keeper) IteratePacketCommitment(ctx sdk.Context, cb func(portID, channelID string, sequence uint64, hash []byte) bool) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, []byte(ibctypes.KeyPacketCommitmentPrefix)) + k.iterateHashes(ctx, iterator, cb) +} + +// GetAllPacketCommitments returns all stored PacketCommitments objects. +func (k Keeper) GetAllPacketCommitments(ctx sdk.Context) (commitments []types.PacketAckCommitment) { + k.IteratePacketCommitment(ctx, func(portID, channelID string, sequence uint64, hash []byte) bool { + pc := types.NewPacketAckCommitment(portID, channelID, sequence, hash) + commitments = append(commitments, pc) + return false + }) + return commitments +} + +// IteratePacketAcknowledgement provides an iterator over all PacketAcknowledgement objects. For each +// aknowledgement, cb will be called. If the cb returns true, the iterator will close +// and stop. +func (k Keeper) IteratePacketAcknowledgement(ctx sdk.Context, cb func(portID, channelID string, sequence uint64, hash []byte) bool) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, []byte(ibctypes.KeyPacketAckPrefix)) + k.iterateHashes(ctx, iterator, cb) +} + +// GetAllPacketAcks returns all stored PacketAcknowledgements objects. +func (k Keeper) GetAllPacketAcks(ctx sdk.Context) (acks []types.PacketAckCommitment) { + k.IteratePacketAcknowledgement(ctx, func(portID, channelID string, sequence uint64, ack []byte) bool { + packetAck := types.NewPacketAckCommitment(portID, channelID, sequence, ack) + acks = append(acks, packetAck) + return false + }) + return acks +} + // IterateChannels provides an iterator over all Channel objects. For each // Channel, cb will be called. If the cb returns true, the iterator will close // and stop. @@ -145,9 +232,10 @@ func (k Keeper) IterateChannels(ctx sdk.Context, cb func(types.IdentifiedChannel for ; iterator.Valid(); iterator.Next() { var channel types.Channel k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &channel) - portID, channelID := ibctypes.MustParseChannelPath(string(iterator.Key())) - if cb(types.IdentifiedChannel{Channel: channel, PortIdentifier: portID, ChannelIdentifier: channelID}) { + portID, channelID := ibctypes.MustParseChannelPath(string(iterator.Key())) + identifiedChannel := types.NewIdentifiedChannel(portID, channelID, channel) + if cb(identifiedChannel) { break } } @@ -171,3 +259,22 @@ func (k Keeper) LookupModuleByChannel(ctx sdk.Context, portID, channelID string) return ibctypes.GetModuleOwner(modules), cap, true } + +// common functionality for IteratePacketCommitment and IteratePacketAcknowledgemen +func (k Keeper) iterateHashes(ctx sdk.Context, iterator db.Iterator, cb func(portID, channelID string, sequence uint64, hash []byte) bool) { + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + keySplit := strings.Split(string(iterator.Key()), "/") + portID := keySplit[2] + channelID := keySplit[4] + + sequence, err := strconv.ParseUint(keySplit[len(keySplit)-1], 10, 64) + if err != nil { + panic(err) + } + + if cb(portID, channelID, sequence, iterator.Value()) { + break + } + } +} diff --git a/x/ibc/04-channel/keeper/keeper_test.go b/x/ibc/04-channel/keeper/keeper_test.go index 52f60c0a4..633f1380e 100644 --- a/x/ibc/04-channel/keeper/keeper_test.go +++ b/x/ibc/04-channel/keeper/keeper_test.go @@ -75,16 +75,11 @@ func (suite *KeeperTestSuite) TestSetChannel() { _, found := suite.chainB.App.IBCKeeper.ChannelKeeper.GetChannel(ctx, testPort1, testChannel1) suite.False(found) - channel := types.Channel{ - State: exported.OPEN, - Ordering: testChannelOrder, - Counterparty: types.Counterparty{ - PortID: testPort2, - ChannelID: testChannel2, - }, - ConnectionHops: []string{testConnectionIDA}, - Version: testChannelVersion, - } + counterparty2 := types.NewCounterparty(testPort2, testChannel2) + channel := types.NewChannel( + exported.INIT, testChannelOrder, + counterparty2, []string{testConnectionIDA}, testChannelVersion, + ) suite.chainB.App.IBCKeeper.ChannelKeeper.SetChannel(ctx, testPort1, testChannel1, channel) storedChannel, found := suite.chainB.App.IBCKeeper.ChannelKeeper.GetChannel(ctx, testPort1, testChannel1) @@ -98,37 +93,27 @@ func (suite KeeperTestSuite) TestGetAllChannels() { counterparty2 := types.NewCounterparty(testPort2, testChannel2) counterparty3 := types.NewCounterparty(testPort3, testChannel3) - channel1 := types.Channel{ - State: exported.INIT, - Ordering: testChannelOrder, - Counterparty: counterparty3, - ConnectionHops: []string{testConnectionIDA}, - Version: testChannelVersion, - } - - channel2 := types.Channel{ - State: exported.INIT, - Ordering: testChannelOrder, - Counterparty: counterparty1, - ConnectionHops: []string{testConnectionIDA}, - Version: testChannelVersion, - } - - channel3 := types.Channel{ - State: exported.CLOSED, - Ordering: testChannelOrder, - Counterparty: counterparty2, - ConnectionHops: []string{testConnectionIDA}, - Version: testChannelVersion, - } + channel1 := types.NewChannel( + exported.INIT, testChannelOrder, + counterparty3, []string{testConnectionIDA}, testChannelVersion, + ) + channel2 := types.NewChannel( + exported.INIT, testChannelOrder, + counterparty1, []string{testConnectionIDA}, testChannelVersion, + ) + channel3 := types.NewChannel( + exported.CLOSED, testChannelOrder, + counterparty2, []string{testConnectionIDA}, testChannelVersion, + ) expChannels := []types.IdentifiedChannel{ - {Channel: channel1, PortIdentifier: testPort1, ChannelIdentifier: testChannel1}, - {Channel: channel2, PortIdentifier: testPort2, ChannelIdentifier: testChannel2}, - {Channel: channel3, PortIdentifier: testPort3, ChannelIdentifier: testChannel3}, + types.NewIdentifiedChannel(testPort1, testChannel1, channel1), + types.NewIdentifiedChannel(testPort2, testChannel2, channel2), + types.NewIdentifiedChannel(testPort3, testChannel3, channel3), } ctx := suite.chainB.GetContext() + suite.chainB.App.IBCKeeper.ChannelKeeper.SetChannel(ctx, testPort1, testChannel1, channel1) suite.chainB.App.IBCKeeper.ChannelKeeper.SetChannel(ctx, testPort2, testChannel2, channel2) suite.chainB.App.IBCKeeper.ChannelKeeper.SetChannel(ctx, testPort3, testChannel3, channel3) @@ -138,6 +123,53 @@ func (suite KeeperTestSuite) TestGetAllChannels() { suite.Require().Equal(expChannels, channels) } +func (suite KeeperTestSuite) TestGetAllSequences() { + seq1 := types.NewPacketSequence(testPort1, testChannel1, 1) + seq2 := types.NewPacketSequence(testPort2, testChannel2, 2) + + expSeqs := []types.PacketSequence{seq1, seq2} + + ctx := suite.chainB.GetContext() + + for _, seq := range expSeqs { + suite.chainB.App.IBCKeeper.ChannelKeeper.SetNextSequenceSend(ctx, seq.PortID, seq.ChannelID, seq.Sequence) + suite.chainB.App.IBCKeeper.ChannelKeeper.SetNextSequenceRecv(ctx, seq.PortID, seq.ChannelID, seq.Sequence) + } + + sendSeqs := suite.chainB.App.IBCKeeper.ChannelKeeper.GetAllPacketSendSeqs(ctx) + recvSeqs := suite.chainB.App.IBCKeeper.ChannelKeeper.GetAllPacketRecvSeqs(ctx) + suite.Require().Len(sendSeqs, 2) + suite.Require().Len(recvSeqs, 2) + + suite.Require().Equal(expSeqs, sendSeqs) + suite.Require().Equal(expSeqs, recvSeqs) +} + +func (suite KeeperTestSuite) TestGetAllCommitmentsAcks() { + ack1 := types.NewPacketAckCommitment(testPort1, testChannel1, 1, []byte("ack")) + ack2 := types.NewPacketAckCommitment(testPort1, testChannel1, 2, []byte("ack")) + comm1 := types.NewPacketAckCommitment(testPort1, testChannel1, 1, []byte("hash")) + comm2 := types.NewPacketAckCommitment(testPort1, testChannel1, 2, []byte("hash")) + + expAcks := []types.PacketAckCommitment{ack1, ack2} + expCommitments := []types.PacketAckCommitment{comm1, comm2} + + ctx := suite.chainB.GetContext() + + for i := 0; i < 2; i++ { + suite.chainB.App.IBCKeeper.ChannelKeeper.SetPacketAcknowledgement(ctx, expAcks[i].PortID, expAcks[i].ChannelID, expAcks[i].Sequence, expAcks[i].Hash) + suite.chainB.App.IBCKeeper.ChannelKeeper.SetPacketCommitment(ctx, expCommitments[i].PortID, expCommitments[i].ChannelID, expCommitments[i].Sequence, expCommitments[i].Hash) + } + + acks := suite.chainB.App.IBCKeeper.ChannelKeeper.GetAllPacketAcks(ctx) + commitments := suite.chainB.App.IBCKeeper.ChannelKeeper.GetAllPacketCommitments(ctx) + suite.Require().Len(acks, 2) + suite.Require().Len(commitments, 2) + + suite.Require().Equal(expAcks, acks) + suite.Require().Equal(expCommitments, commitments) +} + func (suite *KeeperTestSuite) TestSetSequence() { ctx := suite.chainB.GetContext() _, found := suite.chainB.App.IBCKeeper.ChannelKeeper.GetNextSequenceSend(ctx, testPort1, testChannel1) diff --git a/x/ibc/04-channel/keeper/querier.go b/x/ibc/04-channel/keeper/querier.go index 91dfce5ec..37982619e 100644 --- a/x/ibc/04-channel/keeper/querier.go +++ b/x/ibc/04-channel/keeper/querier.go @@ -47,7 +47,7 @@ func QuerierConnectionChannels(ctx sdk.Context, req abci.RequestQuery, k Keeper) connectionChannels := []types.IdentifiedChannel{} for _, channel := range channels { - if channel.Channel.ConnectionHops[0] == params.Connection { + if channel.ConnectionHops[0] == params.Connection { connectionChannels = append(connectionChannels, channel) } } diff --git a/x/ibc/04-channel/types/channel.go b/x/ibc/04-channel/types/channel.go index ebb026bc8..a68e8ec46 100644 --- a/x/ibc/04-channel/types/channel.go +++ b/x/ibc/04-channel/types/channel.go @@ -20,8 +20,8 @@ type Channel struct { // NewChannel creates a new Channel instance func NewChannel( - state exported.State, ordering exported.Order, counterparty Counterparty, - hops []string, version string, + state exported.State, ordering exported.Order, + counterparty Counterparty, hops []string, version string, ) Channel { return Channel{ State: state, @@ -126,3 +126,40 @@ func (c Counterparty) ValidateBasic() error { } return nil } + +// IdentifiedChannel defines a channel with additional port and channel identifier +// fields. +type IdentifiedChannel struct { + ID string `json:"id" yaml:"id"` + PortID string `json:"port_id" yaml:"port_id"` + State exported.State `json:"state" yaml:"state"` + Ordering exported.Order `json:"ordering" yaml:"ordering"` + Counterparty Counterparty `json:"counterparty" yaml:"counterparty"` + ConnectionHops []string `json:"connection_hops" yaml:"connection_hops"` + Version string `json:"version" yaml:"version "` +} + +// NewIdentifiedChannel creates a new IdentifiedChannel instance +func NewIdentifiedChannel(portID, channelID string, ch Channel) IdentifiedChannel { + return IdentifiedChannel{ + ID: channelID, + PortID: portID, + State: ch.State, + Ordering: ch.Ordering, + Counterparty: ch.Counterparty, + ConnectionHops: ch.ConnectionHops, + Version: ch.Version, + } +} + +// ValidateBasic performs a basic validation of the identifiers and channel fields. +func (ic IdentifiedChannel) ValidateBasic() error { + if err := host.DefaultChannelIdentifierValidator(ic.ID); err != nil { + return sdkerrors.Wrap(ErrInvalidChannel, err.Error()) + } + if err := host.DefaultPortIdentifierValidator(ic.PortID); err != nil { + return sdkerrors.Wrap(ErrInvalidChannel, err.Error()) + } + channel := NewChannel(ic.State, ic.Ordering, ic.Counterparty, ic.ConnectionHops, ic.Version) + return channel.ValidateBasic() +} diff --git a/x/ibc/04-channel/types/genesis.go b/x/ibc/04-channel/types/genesis.go new file mode 100644 index 000000000..8e426a06a --- /dev/null +++ b/x/ibc/04-channel/types/genesis.go @@ -0,0 +1,142 @@ +package types + +import ( + "errors" + "fmt" + + host "github.com/cosmos/cosmos-sdk/x/ibc/24-host" +) + +// PacketAckCommitment defines the genesis type necessary to retrieve and store +// acknowlegements. +type PacketAckCommitment struct { + PortID string `json:"port_id" yaml:"port_id"` + ChannelID string `json:"channel_id" yaml:"channel_id"` + Sequence uint64 `json:"sequence" yaml:"sequence"` + Hash []byte `json:"hash" yaml:"hash"` +} + +// NewPacketAckCommitment creates a new PacketAckCommitment instance. +func NewPacketAckCommitment(portID, channelID string, seq uint64, hash []byte) PacketAckCommitment { + return PacketAckCommitment{ + PortID: portID, + ChannelID: channelID, + Sequence: seq, + Hash: hash, + } +} + +// Validate performs basic validation of fields returning an error upon any +// failure. +func (pa PacketAckCommitment) Validate() error { + if len(pa.Hash) == 0 { + return errors.New("hash bytes cannot be empty") + } + return validateGenFields(pa.PortID, pa.ChannelID, pa.Sequence) +} + +// PacketSequence defines the genesis type necessary to retrieve and store +// next send and receive sequences. +type PacketSequence struct { + PortID string `json:"port_id" yaml:"port_id"` + ChannelID string `json:"channel_id" yaml:"channel_id"` + Sequence uint64 `json:"sequence" yaml:"sequence"` +} + +// NewPacketSequence creates a new PacketSequences instance. +func NewPacketSequence(portID, channelID string, seq uint64) PacketSequence { + return PacketSequence{ + PortID: portID, + ChannelID: channelID, + Sequence: seq, + } +} + +// Validate performs basic validation of fields returning an error upon any +// failure. +func (ps PacketSequence) Validate() error { + return validateGenFields(ps.PortID, ps.ChannelID, ps.Sequence) +} + +// GenesisState defines the ibc channel submodule's genesis state. +type GenesisState struct { + Channels []IdentifiedChannel `json:"channels" yaml:"channels"` + Acknowledgements []PacketAckCommitment `json:"acknowledgements" yaml:"acknowledgements"` + Commitments []PacketAckCommitment `json:"commitments" yaml:"commitments"` + SendSequences []PacketSequence `json:"send_sequences" yaml:"send_sequences"` + RecvSequences []PacketSequence `json:"recv_sequences" yaml:"recv_sequences"` +} + +// NewGenesisState creates a GenesisState instance. +func NewGenesisState( + channels []IdentifiedChannel, acks, commitments []PacketAckCommitment, + sendSeqs, recvSeqs []PacketSequence, +) GenesisState { + return GenesisState{ + Channels: channels, + Acknowledgements: acks, + Commitments: commitments, + SendSequences: sendSeqs, + RecvSequences: recvSeqs, + } +} + +// DefaultGenesisState returns the ibc channel submodule's default genesis state. +func DefaultGenesisState() GenesisState { + return GenesisState{ + Channels: []IdentifiedChannel{}, + Acknowledgements: []PacketAckCommitment{}, + Commitments: []PacketAckCommitment{}, + SendSequences: []PacketSequence{}, + RecvSequences: []PacketSequence{}, + } +} + +// Validate performs basic genesis state validation returning an error upon any +// failure. +func (gs GenesisState) Validate() error { + for i, channel := range gs.Channels { + if err := channel.ValidateBasic(); err != nil { + return fmt.Errorf("invalid channel %d: %w", i, err) + } + } + + for i, ack := range gs.Acknowledgements { + if err := ack.Validate(); err != nil { + return fmt.Errorf("invalid acknowledgement %d: %w", i, err) + } + } + + for i, commitment := range gs.Commitments { + if err := commitment.Validate(); err != nil { + return fmt.Errorf("invalid commitment %d: %w", i, err) + } + } + + for i, ss := range gs.SendSequences { + if err := ss.Validate(); err != nil { + return fmt.Errorf("invalid send sequence %d: %w", i, err) + } + } + + for i, rs := range gs.RecvSequences { + if err := rs.Validate(); err != nil { + return fmt.Errorf("invalid receive sequence %d: %w", i, err) + } + } + + return nil +} + +func validateGenFields(portID, channelID string, sequence uint64) error { + if err := host.DefaultPortIdentifierValidator(portID); err != nil { + return err + } + if err := host.DefaultChannelIdentifierValidator(channelID); err != nil { + return err + } + if sequence == 0 { + return errors.New("sequence cannot be 0") + } + return nil +} diff --git a/x/ibc/04-channel/types/genesis_test.go b/x/ibc/04-channel/types/genesis_test.go new file mode 100644 index 000000000..dc9e55ec8 --- /dev/null +++ b/x/ibc/04-channel/types/genesis_test.go @@ -0,0 +1,135 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/exported" +) + +const ( + testPort1 = "firstport" + testPort2 = "secondport" + testConnectionIDA = "connectionidatob" + + testChannel1 = "firstchannel" + testChannel2 = "secondchannel" + + testChannelOrder = exported.ORDERED + testChannelVersion = "1.0" +) + +func TestValidateGenesis(t *testing.T) { + counterparty1 := NewCounterparty(testPort1, testChannel1) + counterparty2 := NewCounterparty(testPort2, testChannel2) + testCases := []struct { + name string + genState GenesisState + expPass bool + }{ + { + name: "default", + genState: DefaultGenesisState(), + expPass: true, + }, + { + name: "valid genesis", + genState: NewGenesisState( + []IdentifiedChannel{ + NewIdentifiedChannel( + testPort1, testChannel1, NewChannel( + exported.INIT, testChannelOrder, counterparty2, []string{testConnectionIDA}, testChannelVersion, + ), + ), + NewIdentifiedChannel( + testPort2, testChannel2, NewChannel( + exported.INIT, testChannelOrder, counterparty1, []string{testConnectionIDA}, testChannelVersion, + ), + ), + }, + []PacketAckCommitment{ + NewPacketAckCommitment(testPort2, testChannel2, 1, []byte("ack")), + }, + []PacketAckCommitment{ + NewPacketAckCommitment(testPort1, testChannel1, 1, []byte("commit_hash")), + }, + []PacketSequence{ + NewPacketSequence(testPort1, testChannel1, 1), + }, + []PacketSequence{ + NewPacketSequence(testPort2, testChannel2, 1), + }, + ), + expPass: true, + }, + { + name: "invalid channel", + genState: GenesisState{ + Channels: []IdentifiedChannel{ + NewIdentifiedChannel( + testPort1, "testChannel1", NewChannel( + exported.INIT, testChannelOrder, counterparty2, []string{testConnectionIDA}, testChannelVersion, + ), + ), + }, + }, + expPass: false, + }, + { + name: "invalid ack", + genState: GenesisState{ + Acknowledgements: []PacketAckCommitment{ + NewPacketAckCommitment(testPort2, testChannel2, 1, nil), + }, + }, + expPass: false, + }, + { + name: "invalid commitment", + genState: GenesisState{ + Commitments: []PacketAckCommitment{ + NewPacketAckCommitment(testPort1, testChannel1, 1, nil), + }, + }, + expPass: false, + }, + { + name: "invalid send seq", + genState: GenesisState{ + SendSequences: []PacketSequence{ + NewPacketSequence(testPort1, testChannel1, 0), + }, + }, + expPass: false, + }, + { + name: "invalid recv seq", + genState: GenesisState{ + RecvSequences: []PacketSequence{ + NewPacketSequence(testPort1, "testChannel1", 1), + }, + }, + expPass: false, + }, + { + name: "invalid recv seq 2", + genState: GenesisState{ + RecvSequences: []PacketSequence{ + NewPacketSequence("testPort1", testChannel1, 1), + }, + }, + expPass: false, + }, + } + + for _, tc := range testCases { + tc := tc + err := tc.genState.Validate() + if tc.expPass { + require.NoError(t, err, tc.name) + } else { + require.Error(t, err, tc.name) + } + } +} diff --git a/x/ibc/04-channel/types/querier.go b/x/ibc/04-channel/types/querier.go index 289a5bfc3..7f5313f27 100644 --- a/x/ibc/04-channel/types/querier.go +++ b/x/ibc/04-channel/types/querier.go @@ -16,12 +16,6 @@ const ( QueryConnectionChannels = "connection-channels" ) -type IdentifiedChannel struct { - Channel Channel `json:"channel_end" yaml:"channel_end"` - PortIdentifier string `json:"port_identifier" yaml:"port_identifier"` - ChannelIdentifier string `json:"channel_identifier" yaml:"channel_identifier"` -} - // ChannelResponse defines the client query response for a channel which also // includes a proof,its path and the height from which the proof was retrieved. type ChannelResponse struct { @@ -36,7 +30,8 @@ func NewChannelResponse( portID, channelID string, channel Channel, proof *merkle.Proof, height int64, ) ChannelResponse { return ChannelResponse{ - Channel: IdentifiedChannel{Channel: channel, PortIdentifier: portID, ChannelIdentifier: channelID}, + + Channel: NewIdentifiedChannel(portID, channelID, channel), Proof: commitmenttypes.MerkleProof{Proof: proof}, ProofPath: commitmenttypes.NewMerklePath(strings.Split(ibctypes.ChannelPath(portID, channelID), "/")), ProofHeight: uint64(height), diff --git a/x/ibc/genesis.go b/x/ibc/genesis.go index 3d2fc3676..673af8856 100644 --- a/x/ibc/genesis.go +++ b/x/ibc/genesis.go @@ -4,12 +4,14 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" client "github.com/cosmos/cosmos-sdk/x/ibc/02-client" connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection" + channel "github.com/cosmos/cosmos-sdk/x/ibc/04-channel" ) // GenesisState defines the ibc module's genesis state. type GenesisState struct { ClientGenesis client.GenesisState `json:"client_genesis" yaml:"client_genesis"` ConnectionGenesis connection.GenesisState `json:"connection_genesis" yaml:"connection_genesis"` + ChannelGenesis channel.GenesisState `json:"channel_genesis" yaml:"channel_genesis"` } // DefaultGenesisState returns the ibc module's default genesis state. @@ -17,6 +19,7 @@ func DefaultGenesisState() GenesisState { return GenesisState{ ClientGenesis: client.DefaultGenesisState(), ConnectionGenesis: connection.DefaultGenesisState(), + ChannelGenesis: channel.DefaultGenesisState(), } } @@ -27,7 +30,11 @@ func (gs GenesisState) Validate() error { return err } - return gs.ConnectionGenesis.Validate() + if err := gs.ConnectionGenesis.Validate(); err != nil { + return err + } + + return gs.ChannelGenesis.Validate() } // InitGenesis initializes the ibc state from a provided genesis @@ -35,6 +42,7 @@ func (gs GenesisState) Validate() error { func InitGenesis(ctx sdk.Context, k Keeper, gs GenesisState) { client.InitGenesis(ctx, k.ClientKeeper, gs.ClientGenesis) connection.InitGenesis(ctx, k.ConnectionKeeper, gs.ConnectionGenesis) + channel.InitGenesis(ctx, k.ChannelKeeper, gs.ChannelGenesis) } // ExportGenesis returns the ibc exported genesis. @@ -42,5 +50,6 @@ func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState { return GenesisState{ ClientGenesis: client.ExportGenesis(ctx, k.ClientKeeper), ConnectionGenesis: connection.ExportGenesis(ctx, k.ConnectionKeeper), + ChannelGenesis: channel.ExportGenesis(ctx, k.ChannelKeeper), } } diff --git a/x/ibc/genesis_test.go b/x/ibc/genesis_test.go index e1ea2abc9..cb53bc3df 100644 --- a/x/ibc/genesis_test.go +++ b/x/ibc/genesis_test.go @@ -6,6 +6,8 @@ import ( "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection" connectionexported "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/exported" + channel "github.com/cosmos/cosmos-sdk/x/ibc/04-channel" + channelexported "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/exported" ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types" localhosttypes "github.com/cosmos/cosmos-sdk/x/ibc/09-localhost/types" commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types" @@ -50,6 +52,28 @@ func (suite *IBCTestSuite) TestValidateGenesis() { connection.NewConnectionPaths(clientID, []string{ibctypes.ConnectionPath(connectionID)}), }, ), + ChannelGenesis: channel.NewGenesisState( + []channel.IdentifiedChannel{ + channel.NewIdentifiedChannel( + port1, channel1, channel.NewChannel( + channelexported.INIT, channelOrder, + channel.NewCounterparty(port2, channel2), []string{connectionID}, channelVersion, + ), + ), + }, + []channel.PacketAckCommitment{ + channel.NewPacketAckCommitment(port2, channel2, 1, []byte("ack")), + }, + []channel.PacketAckCommitment{ + channel.NewPacketAckCommitment(port1, channel1, 1, []byte("commit_hash")), + }, + []channel.PacketSequence{ + channel.NewPacketSequence(port1, channel1, 1), + }, + []channel.PacketSequence{ + channel.NewPacketSequence(port2, channel2, 1), + }, + ), }, expPass: true, }, @@ -82,6 +106,19 @@ func (suite *IBCTestSuite) TestValidateGenesis() { }, expPass: false, }, + { + name: "invalid channel genesis", + genState: ibc.GenesisState{ + ClientGenesis: client.DefaultGenesisState(), + ConnectionGenesis: connection.DefaultGenesisState(), + ChannelGenesis: channel.GenesisState{ + Acknowledgements: []channel.PacketAckCommitment{ + channel.NewPacketAckCommitment("portID", channel1, 1, []byte("ack")), + }, + }, + }, + expPass: false, + }, } for _, tc := range testCases { diff --git a/x/ibc/ibc_test.go b/x/ibc/ibc_test.go index 00dcb3482..34afbe7f2 100644 --- a/x/ibc/ibc_test.go +++ b/x/ibc/ibc_test.go @@ -15,6 +15,7 @@ import ( "github.com/cosmos/cosmos-sdk/store/cachekv" "github.com/cosmos/cosmos-sdk/store/dbadapter" sdk "github.com/cosmos/cosmos-sdk/types" + channelexported "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/exported" ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types" ) @@ -24,6 +25,15 @@ const ( connectionID2 = "connectionidtwo" clientID2 = "clientidtwo" + port1 = "firstport" + port2 = "secondport" + + channel1 = "firstchannel" + channel2 = "secondchannel" + + channelOrder = channelexported.ORDERED + channelVersion = "1.0" + trustingPeriod time.Duration = time.Hour * 24 * 7 * 2 ubdPeriod time.Duration = time.Hour * 24 * 7 * 3 maxClockDrift time.Duration = time.Second * 10 diff --git a/x/ibc/types/keys.go b/x/ibc/types/keys.go index 4a08904ea..39e7e006c 100644 --- a/x/ibc/types/keys.go +++ b/x/ibc/types/keys.go @@ -144,12 +144,6 @@ func KeyChannel(portID, channelID string) []byte { return []byte(ChannelPath(portID, channelID)) } -// KeyChannelCapabilityPath returns the store key for the capability key of a -// particular channel binded to a specific port -func KeyChannelCapabilityPath(portID, channelID string) []byte { - return []byte(ChannelCapabilityPath(portID, channelID)) -} - // KeyNextSequenceSend returns the store key for the send sequence of a particular // channel binded to a specific port func KeyNextSequenceSend(portID, channelID string) []byte {