cosmos-sdk/x/ibc/core/04-channel/keeper/handshake.go

455 lines
16 KiB
Go

package keeper
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types"
connectiontypes "github.com/cosmos/cosmos-sdk/x/ibc/core/03-connection/types"
"github.com/cosmos/cosmos-sdk/x/ibc/core/04-channel/types"
porttypes "github.com/cosmos/cosmos-sdk/x/ibc/core/05-port/types"
host "github.com/cosmos/cosmos-sdk/x/ibc/core/24-host"
"github.com/cosmos/cosmos-sdk/x/ibc/core/exported"
)
// CounterpartyHops returns the connection hops of the counterparty channel.
// The counterparty hops are stored in the inverse order as the channel's.
func (k Keeper) CounterpartyHops(ctx sdk.Context, ch types.Channel) ([]string, bool) {
counterPartyHops := make([]string, len(ch.ConnectionHops))
for i, hop := range ch.ConnectionHops {
conn, found := k.connectionKeeper.GetConnection(ctx, hop)
if !found {
return []string{}, false
}
counterPartyHops[len(counterPartyHops)-1-i] = conn.GetCounterparty().GetConnectionID()
}
return counterPartyHops, true
}
// ChanOpenInit is called by a module to initiate a channel opening handshake with
// a module on another chain.
func (k Keeper) ChanOpenInit(
ctx sdk.Context,
order types.Order,
connectionHops []string,
portID,
channelID string,
portCap *capabilitytypes.Capability,
counterparty types.Counterparty,
version string,
) (*capabilitytypes.Capability, error) {
// channel identifier and connection hop length checked on msg.ValidateBasic()
_, found := k.GetChannel(ctx, portID, channelID)
if found {
return nil, sdkerrors.Wrapf(types.ErrChannelExists, "port ID (%s) channel ID (%s)", portID, channelID)
}
connectionEnd, found := k.connectionKeeper.GetConnection(ctx, connectionHops[0])
if !found {
return nil, sdkerrors.Wrap(connectiontypes.ErrConnectionNotFound, connectionHops[0])
}
if len(connectionEnd.GetVersions()) != 1 {
return nil, sdkerrors.Wrapf(
connectiontypes.ErrInvalidVersion,
"single version must be negotiated on connection before opening channel, got: %v",
connectionEnd.GetVersions(),
)
}
if !connectiontypes.VerifySupportedFeature(connectionEnd.GetVersions()[0], order.String()) {
return nil, sdkerrors.Wrapf(
connectiontypes.ErrInvalidVersion,
"connection version %s does not support channel ordering: %s",
connectionEnd.GetVersions()[0], order.String(),
)
}
if !k.portKeeper.Authenticate(ctx, portCap, portID) {
return nil, sdkerrors.Wrapf(porttypes.ErrInvalidPort, "caller does not own port capability for port ID %s", portID)
}
channel := types.NewChannel(types.INIT, order, counterparty, connectionHops, version)
k.SetChannel(ctx, portID, channelID, channel)
capKey, err := k.scopedKeeper.NewCapability(ctx, host.ChannelCapabilityPath(portID, channelID))
if err != nil {
return nil, sdkerrors.Wrapf(err, "could not create channel capability for port ID %s and channel ID %s", portID, channelID)
}
k.SetNextSequenceSend(ctx, portID, channelID, 1)
k.SetNextSequenceRecv(ctx, portID, channelID, 1)
k.SetNextSequenceAck(ctx, portID, channelID, 1)
k.Logger(ctx).Info(fmt.Sprintf("channel (port-id: %s, channel-id: %s) state updated: NONE -> INIT", portID, channelID))
return capKey, nil
}
// ChanOpenTry is called by a module to accept the first step of a channel opening
// handshake initiated by a module on another chain.
func (k Keeper) ChanOpenTry(
ctx sdk.Context,
order types.Order,
connectionHops []string,
portID,
desiredChannelID,
counterpartyChosenChannelID string,
portCap *capabilitytypes.Capability,
counterparty types.Counterparty,
version,
counterpartyVersion string,
proofInit []byte,
proofHeight exported.Height,
) (*capabilitytypes.Capability, error) {
// channel identifier and connection hop length checked on msg.ValidateBasic()
previousChannel, found := k.GetChannel(ctx, portID, desiredChannelID)
if found && !(previousChannel.State == types.INIT &&
previousChannel.Ordering == order &&
previousChannel.Counterparty.PortId == counterparty.PortId &&
previousChannel.Counterparty.ChannelId == counterparty.ChannelId &&
previousChannel.ConnectionHops[0] == connectionHops[0] &&
previousChannel.Version == version) {
return nil, sdkerrors.Wrap(types.ErrInvalidChannel, "cannot relay connection attempt")
}
if !k.portKeeper.Authenticate(ctx, portCap, portID) {
return nil, sdkerrors.Wrapf(porttypes.ErrInvalidPort, "caller does not own port capability for port ID %s", portID)
}
connectionEnd, found := k.connectionKeeper.GetConnection(ctx, connectionHops[0])
if !found {
return nil, sdkerrors.Wrap(connectiontypes.ErrConnectionNotFound, connectionHops[0])
}
if connectionEnd.GetState() != int32(connectiontypes.OPEN) {
return nil, sdkerrors.Wrapf(
connectiontypes.ErrInvalidConnectionState,
"connection state is not OPEN (got %s)", connectiontypes.State(connectionEnd.GetState()).String(),
)
}
if len(connectionEnd.GetVersions()) != 1 {
return nil, sdkerrors.Wrapf(
connectiontypes.ErrInvalidVersion,
"single version must be negotiated on connection before opening channel, got: %v",
connectionEnd.GetVersions(),
)
}
if !connectiontypes.VerifySupportedFeature(connectionEnd.GetVersions()[0], order.String()) {
return nil, sdkerrors.Wrapf(
connectiontypes.ErrInvalidVersion,
"connection version %s does not support channel ordering: %s",
connectionEnd.GetVersions()[0], order.String(),
)
}
// If the channel id chosen for this channel end by the counterparty is empty then
// flexible channel identifier selection is allowed by using the desired channel id.
// Otherwise the desiredChannelID must match the counterpartyChosenChannelID.
if counterpartyChosenChannelID != "" && counterpartyChosenChannelID != desiredChannelID {
return nil, sdkerrors.Wrapf(
types.ErrInvalidChannelIdentifier,
"counterparty chosen channel ID (%s) must be empty or equal to the desired channel ID (%s)", counterpartyChosenChannelID, desiredChannelID,
)
}
// NOTE: this step has been switched with the one below to reverse the connection
// hops
channel := types.NewChannel(types.TRYOPEN, order, counterparty, connectionHops, version)
counterpartyHops, found := k.CounterpartyHops(ctx, channel)
if !found {
// should not reach here, connectionEnd was able to be retrieved above
panic("cannot find connection")
}
// expectedCounterpaty is the counterparty of the counterparty's channel end
// (i.e self)
expectedCounterparty := types.NewCounterparty(portID, counterpartyChosenChannelID)
expectedChannel := types.NewChannel(
types.INIT, channel.Ordering, expectedCounterparty,
counterpartyHops, counterpartyVersion,
)
if err := k.connectionKeeper.VerifyChannelState(
ctx, connectionEnd, proofHeight, proofInit,
counterparty.PortId, counterparty.ChannelId, expectedChannel,
); err != nil {
return nil, err
}
k.SetChannel(ctx, portID, desiredChannelID, channel)
capKey, err := k.scopedKeeper.NewCapability(ctx, host.ChannelCapabilityPath(portID, desiredChannelID))
if err != nil {
return nil, sdkerrors.Wrapf(err, "could not create channel capability for port ID %s and channel ID %s", portID, desiredChannelID)
}
k.SetNextSequenceSend(ctx, portID, desiredChannelID, 1)
k.SetNextSequenceRecv(ctx, portID, desiredChannelID, 1)
k.SetNextSequenceAck(ctx, portID, desiredChannelID, 1)
k.Logger(ctx).Info(fmt.Sprintf("channel (port-id: %s, channel-id: %s) state updated: NONE -> TRYOPEN", portID, desiredChannelID))
return capKey, nil
}
// ChanOpenAck is called by the handshake-originating module to acknowledge the
// acceptance of the initial request by the counterparty module on the other chain.
func (k Keeper) ChanOpenAck(
ctx sdk.Context,
portID,
channelID string,
chanCap *capabilitytypes.Capability,
counterpartyVersion,
counterpartyChannelID string,
proofTry []byte,
proofHeight exported.Height,
) error {
channel, found := k.GetChannel(ctx, portID, channelID)
if !found {
return sdkerrors.Wrapf(types.ErrChannelNotFound, "port ID (%s) channel ID (%s)", portID, channelID)
}
if !(channel.State == types.INIT || channel.State == types.TRYOPEN) {
return sdkerrors.Wrapf(
types.ErrInvalidChannelState,
"channel state should be INIT or TRYOPEN (got %s)", channel.State.String(),
)
}
if !k.scopedKeeper.AuthenticateCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)) {
return sdkerrors.Wrapf(types.ErrChannelCapabilityNotFound, "caller does not own capability for channel, port ID (%s) channel ID (%s)", portID, channelID)
}
// If the previously set channel end allowed for the counterparty to select its own
// channel identifier then we use the counterpartyChannelID. Otherwise the
// counterpartyChannelID must match the previously set counterparty channel ID.
if channel.Counterparty.ChannelId != "" && counterpartyChannelID != channel.Counterparty.ChannelId {
return sdkerrors.Wrapf(
types.ErrInvalidChannelIdentifier,
"counterparty channel identifier (%s) must be equal to stored channel ID for counterparty (%s)", counterpartyChannelID, channel.Counterparty.ChannelId,
)
}
connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0])
if !found {
return sdkerrors.Wrap(connectiontypes.ErrConnectionNotFound, channel.ConnectionHops[0])
}
if connectionEnd.GetState() != int32(connectiontypes.OPEN) {
return sdkerrors.Wrapf(
connectiontypes.ErrInvalidConnectionState,
"connection state is not OPEN (got %s)", connectiontypes.State(connectionEnd.GetState()).String(),
)
}
counterpartyHops, found := k.CounterpartyHops(ctx, channel)
if !found {
// should not reach here, connectionEnd was able to be retrieved above
panic("cannot find connection")
}
// counterparty of the counterparty channel end (i.e self)
expectedCounterparty := types.NewCounterparty(portID, channelID)
expectedChannel := types.NewChannel(
types.TRYOPEN, channel.Ordering, expectedCounterparty,
counterpartyHops, counterpartyVersion,
)
if err := k.connectionKeeper.VerifyChannelState(
ctx, connectionEnd, proofHeight, proofTry,
channel.Counterparty.PortId, counterpartyChannelID,
expectedChannel,
); err != nil {
return err
}
k.Logger(ctx).Info(fmt.Sprintf("channel (port-id: %s, channel-id: %s) state updated: %s -> OPEN", portID, channelID, channel.State))
channel.State = types.OPEN
channel.Version = counterpartyVersion
channel.Counterparty.ChannelId = counterpartyChannelID
k.SetChannel(ctx, portID, channelID, channel)
return nil
}
// ChanOpenConfirm is called by the counterparty module to close their end of the
// channel, since the other end has been closed.
func (k Keeper) ChanOpenConfirm(
ctx sdk.Context,
portID,
channelID string,
chanCap *capabilitytypes.Capability,
proofAck []byte,
proofHeight exported.Height,
) error {
channel, found := k.GetChannel(ctx, portID, channelID)
if !found {
return sdkerrors.Wrapf(types.ErrChannelNotFound, "port ID (%s) channel ID (%s)", portID, channelID)
}
if channel.State != types.TRYOPEN {
return sdkerrors.Wrapf(
types.ErrInvalidChannelState,
"channel state is not TRYOPEN (got %s)", channel.State.String(),
)
}
if !k.scopedKeeper.AuthenticateCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)) {
return sdkerrors.Wrapf(types.ErrChannelCapabilityNotFound, "caller does not own capability for channel, port ID (%s) channel ID (%s)", portID, channelID)
}
connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0])
if !found {
return sdkerrors.Wrap(connectiontypes.ErrConnectionNotFound, channel.ConnectionHops[0])
}
if connectionEnd.GetState() != int32(connectiontypes.OPEN) {
return sdkerrors.Wrapf(
connectiontypes.ErrInvalidConnectionState,
"connection state is not OPEN (got %s)", connectiontypes.State(connectionEnd.GetState()).String(),
)
}
counterpartyHops, found := k.CounterpartyHops(ctx, channel)
if !found {
// Should not reach here, connectionEnd was able to be retrieved above
panic("cannot find connection")
}
counterparty := types.NewCounterparty(portID, channelID)
expectedChannel := types.NewChannel(
types.OPEN, channel.Ordering, counterparty,
counterpartyHops, channel.Version,
)
if err := k.connectionKeeper.VerifyChannelState(
ctx, connectionEnd, proofHeight, proofAck,
channel.Counterparty.PortId, channel.Counterparty.ChannelId,
expectedChannel,
); err != nil {
return err
}
channel.State = types.OPEN
k.SetChannel(ctx, portID, channelID, channel)
k.Logger(ctx).Info(fmt.Sprintf("channel (port-id: %s, channel-id: %s) state updated: TRYOPEN -> OPEN", portID, channelID))
return nil
}
// Closing Handshake
//
// This section defines the set of functions required to close a channel handshake
// as defined in https://github.com/cosmos/ics/tree/master/spec/ics-004-channel-and-packet-semantics#closing-handshake
// ChanCloseInit is called by either module to close their end of the channel. Once
// closed, channels cannot be reopened.
func (k Keeper) ChanCloseInit(
ctx sdk.Context,
portID,
channelID string,
chanCap *capabilitytypes.Capability,
) error {
if !k.scopedKeeper.AuthenticateCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)) {
return sdkerrors.Wrapf(types.ErrChannelCapabilityNotFound, "caller does not own capability for channel, port ID (%s) channel ID (%s)", portID, channelID)
}
channel, found := k.GetChannel(ctx, portID, channelID)
if !found {
return sdkerrors.Wrapf(types.ErrChannelNotFound, "port ID (%s) channel ID (%s)", portID, channelID)
}
if channel.State == types.CLOSED {
return sdkerrors.Wrap(types.ErrInvalidChannelState, "channel is already CLOSED")
}
connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0])
if !found {
return sdkerrors.Wrap(connectiontypes.ErrConnectionNotFound, channel.ConnectionHops[0])
}
if connectionEnd.GetState() != int32(connectiontypes.OPEN) {
return sdkerrors.Wrapf(
connectiontypes.ErrInvalidConnectionState,
"connection state is not OPEN (got %s)", connectiontypes.State(connectionEnd.GetState()).String(),
)
}
k.Logger(ctx).Info(fmt.Sprintf("channel (port-id: %s, channel-id: %s) state updated: %s -> CLOSED", portID, channelID, channel.State))
channel.State = types.CLOSED
k.SetChannel(ctx, portID, channelID, channel)
return nil
}
// ChanCloseConfirm is called by the counterparty module to close their end of the
// channel, since the other end has been closed.
func (k Keeper) ChanCloseConfirm(
ctx sdk.Context,
portID,
channelID string,
chanCap *capabilitytypes.Capability,
proofInit []byte,
proofHeight exported.Height,
) error {
if !k.scopedKeeper.AuthenticateCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)) {
return sdkerrors.Wrap(types.ErrChannelCapabilityNotFound, "caller does not own capability for channel, port ID (%s) channel ID (%s)")
}
channel, found := k.GetChannel(ctx, portID, channelID)
if !found {
return sdkerrors.Wrapf(types.ErrChannelNotFound, "port ID (%s) channel ID (%s)", portID, channelID)
}
if channel.State == types.CLOSED {
return sdkerrors.Wrap(types.ErrInvalidChannelState, "channel is already CLOSED")
}
connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0])
if !found {
return sdkerrors.Wrap(connectiontypes.ErrConnectionNotFound, channel.ConnectionHops[0])
}
if connectionEnd.GetState() != int32(connectiontypes.OPEN) {
return sdkerrors.Wrapf(
connectiontypes.ErrInvalidConnectionState,
"connection state is not OPEN (got %s)", connectiontypes.State(connectionEnd.GetState()).String(),
)
}
counterpartyHops, found := k.CounterpartyHops(ctx, channel)
if !found {
// Should not reach here, connectionEnd was able to be retrieved above
panic("cannot find connection")
}
counterparty := types.NewCounterparty(portID, channelID)
expectedChannel := types.NewChannel(
types.CLOSED, channel.Ordering, counterparty,
counterpartyHops, channel.Version,
)
if err := k.connectionKeeper.VerifyChannelState(
ctx, connectionEnd, proofHeight, proofInit,
channel.Counterparty.PortId, channel.Counterparty.ChannelId,
expectedChannel,
); err != nil {
return err
}
k.Logger(ctx).Info(fmt.Sprintf("channel (port-id: %s, channel-id: %s) state updated: %s -> CLOSED", portID, channelID, channel.State))
channel.State = types.CLOSED
k.SetChannel(ctx, portID, channelID, channel)
return nil
}