github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/ibc-go/modules/core/04-channel/keeper/handshake.go (about)

     1  package keeper
     2  
     3  import (
     4  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
     5  	sdkerrors "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/errors"
     6  	capabilitytypes "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/capability/types"
     7  	connectiontypes "github.com/fibonacci-chain/fbc/libs/ibc-go/modules/core/03-connection/types"
     8  	"github.com/fibonacci-chain/fbc/libs/ibc-go/modules/core/04-channel/types"
     9  	porttypes "github.com/fibonacci-chain/fbc/libs/ibc-go/modules/core/05-port/types"
    10  	host "github.com/fibonacci-chain/fbc/libs/ibc-go/modules/core/24-host"
    11  	"github.com/fibonacci-chain/fbc/libs/ibc-go/modules/core/exported"
    12  )
    13  
    14  // CounterpartyHops returns the connection hops of the counterparty channel.
    15  // The counterparty hops are stored in the inverse order as the channel's.
    16  // NOTE: Since connectionHops only supports single connection channels for now,
    17  // this function requires that connection hops only contain a single connection id
    18  func (k Keeper) CounterpartyHops(ctx sdk.Context, ch types.Channel) ([]string, bool) {
    19  	// Return empty array if connection hops is more than one
    20  	// ConnectionHops length should be verified earlier
    21  	if len(ch.ConnectionHops) != 1 {
    22  		return []string{}, false
    23  	}
    24  	counterpartyHops := make([]string, 1)
    25  	hop := ch.ConnectionHops[0]
    26  	conn, found := k.connectionKeeper.GetConnection(ctx, hop)
    27  	if !found {
    28  		return []string{}, false
    29  	}
    30  
    31  	counterpartyHops[0] = conn.GetCounterparty().GetConnectionID()
    32  	return counterpartyHops, true
    33  }
    34  
    35  // ChanOpenInit is called by a module to initiate a channel opening handshake with
    36  // a module on another chain. The counterparty channel identifier is validated to be
    37  // empty in msg validation.
    38  func (k Keeper) ChanOpenInit(
    39  	ctx sdk.Context,
    40  	order types.Order,
    41  	connectionHops []string,
    42  	portID string,
    43  	portCap *capabilitytypes.Capability,
    44  	counterparty types.Counterparty,
    45  	version string,
    46  ) (string, *capabilitytypes.Capability, error) {
    47  	// connection hop length checked on msg.ValidateBasic()
    48  	connectionEnd, found := k.connectionKeeper.GetConnection(ctx, connectionHops[0])
    49  	if !found {
    50  		return "", nil, sdkerrors.Wrap(connectiontypes.ErrConnectionNotFound, connectionHops[0])
    51  	}
    52  
    53  	getVersions := connectionEnd.GetVersions()
    54  	if len(getVersions) != 1 {
    55  		return "", nil, sdkerrors.Wrapf(
    56  			connectiontypes.ErrInvalidVersion,
    57  			"single version must be negotiated on connection before opening channel, got: %v",
    58  			getVersions,
    59  		)
    60  	}
    61  
    62  	if !connectiontypes.VerifySupportedFeature(getVersions[0], order.String()) {
    63  		return "", nil, sdkerrors.Wrapf(
    64  			connectiontypes.ErrInvalidVersion,
    65  			"connection version %s does not support channel ordering: %s",
    66  			getVersions[0], order.String(),
    67  		)
    68  	}
    69  
    70  	if !k.portKeeper.Authenticate(ctx, portCap, portID) {
    71  		return "", nil, sdkerrors.Wrapf(porttypes.ErrInvalidPort, "caller does not own port capability for port ID %s", portID)
    72  	}
    73  
    74  	channelID := k.GenerateChannelIdentifier(ctx)
    75  	channel := types.NewChannel(types.INIT, order, counterparty, connectionHops, version)
    76  	k.SetChannel(ctx, portID, channelID, channel)
    77  
    78  	capKey, err := k.scopedKeeper.NewCapability(ctx, host.ChannelCapabilityPath(portID, channelID))
    79  	if err != nil {
    80  		return "", nil, sdkerrors.Wrapf(err, "could not create channel capability for port ID %s and channel ID %s", portID, channelID)
    81  	}
    82  
    83  	k.SetNextSequenceSend(ctx, portID, channelID, 1)
    84  	k.SetNextSequenceRecv(ctx, portID, channelID, 1)
    85  	k.SetNextSequenceAck(ctx, portID, channelID, 1)
    86  
    87  	k.Logger(ctx).Info("channel state updated", "port-id", portID, "channel-id", channelID, "previous-state", "NONE", "new-state", "INIT")
    88  
    89  	ctx.EventManager().EmitEvents(sdk.Events{
    90  		sdk.NewEvent(
    91  			types.EventTypeChannelOpenInit,
    92  			sdk.NewAttribute(types.AttributeKeyPortID, portID),
    93  			sdk.NewAttribute(types.AttributeKeyChannelID, channelID),
    94  			sdk.NewAttribute(types.AttributeCounterpartyPortID, counterparty.PortId),
    95  			sdk.NewAttribute(types.AttributeCounterpartyChannelID, counterparty.ChannelId),
    96  			sdk.NewAttribute(types.AttributeKeyConnectionID, connectionHops[0]),
    97  		),
    98  	})
    99  
   100  	return channelID, capKey, nil
   101  }
   102  
   103  // ChanOpenTry is called by a module to accept the first step of a channel opening
   104  // handshake initiated by a module on another chain.
   105  func (k Keeper) ChanOpenTryV2(
   106  	ctx sdk.Context,
   107  	order types.Order,
   108  	connectionHops []string,
   109  	portID,
   110  	previousChannelID string,
   111  	portCap *capabilitytypes.Capability,
   112  	counterparty types.Counterparty,
   113  	version,
   114  	counterpartyVersion string,
   115  	proofInit []byte,
   116  	proofHeight exported.Height,
   117  ) (string, *capabilitytypes.Capability, error) {
   118  	var (
   119  		previousChannel      types.Channel
   120  		previousChannelFound bool
   121  	)
   122  
   123  	channelID := previousChannelID
   124  
   125  	// empty channel identifier indicates continuing a previous channel handshake
   126  	if previousChannelID != "" {
   127  		// channel identifier and connection hop length checked on msg.ValidateBasic()
   128  		// ensure that the previous channel exists
   129  		previousChannel, previousChannelFound = k.GetChannel(ctx, portID, previousChannelID)
   130  		if !previousChannelFound {
   131  			return "", nil, sdkerrors.Wrapf(types.ErrInvalidChannel, "previous channel does not exist for supplied previous channelID %s", previousChannelID)
   132  		}
   133  		// previous channel must use the same fields
   134  		if !(previousChannel.Ordering == order &&
   135  			previousChannel.Counterparty.PortId == counterparty.PortId &&
   136  			previousChannel.Counterparty.ChannelId == "" &&
   137  			previousChannel.ConnectionHops[0] == connectionHops[0] &&
   138  			previousChannel.Version == version) {
   139  			return "", nil, sdkerrors.Wrap(types.ErrInvalidChannel, "channel fields mismatch previous channel fields")
   140  		}
   141  
   142  		if previousChannel.State != types.INIT {
   143  			return "", nil, sdkerrors.Wrapf(types.ErrInvalidChannelState, "previous channel state is in %s, expected INIT", previousChannel.State)
   144  		}
   145  
   146  	} else {
   147  		// generate a new channel
   148  		channelID = k.GenerateChannelIdentifier(ctx)
   149  	}
   150  
   151  	if !k.portKeeper.Authenticate(ctx, portCap, portID) {
   152  		return "", nil, sdkerrors.Wrapf(porttypes.ErrInvalidPort, "caller does not own port capability for port ID %s", portID)
   153  	}
   154  
   155  	connectionEnd, found := k.connectionKeeper.GetConnection(ctx, connectionHops[0])
   156  	if !found {
   157  		return "", nil, sdkerrors.Wrap(connectiontypes.ErrConnectionNotFound, connectionHops[0])
   158  	}
   159  
   160  	if connectionEnd.GetState() != int32(connectiontypes.OPEN) {
   161  		return "", nil, sdkerrors.Wrapf(
   162  			connectiontypes.ErrInvalidConnectionState,
   163  			"connection state is not OPEN (got %s)", connectiontypes.State(connectionEnd.GetState()).String(),
   164  		)
   165  	}
   166  
   167  	getVersions := connectionEnd.GetVersions()
   168  	if len(getVersions) != 1 {
   169  		return "", nil, sdkerrors.Wrapf(
   170  			connectiontypes.ErrInvalidVersion,
   171  			"single version must be negotiated on connection before opening channel, got: %v",
   172  			getVersions,
   173  		)
   174  	}
   175  
   176  	if !connectiontypes.VerifySupportedFeature(getVersions[0], order.String()) {
   177  		return "", nil, sdkerrors.Wrapf(
   178  			connectiontypes.ErrInvalidVersion,
   179  			"connection version %s does not support channel ordering: %s",
   180  			getVersions[0], order.String(),
   181  		)
   182  	}
   183  
   184  	// NOTE: this step has been switched with the one below to reverse the connection
   185  	// hops
   186  	channel := types.NewChannel(types.TRYOPEN, order, counterparty, connectionHops, version)
   187  
   188  	counterpartyHops, found := k.CounterpartyHops(ctx, channel)
   189  	if !found {
   190  		// should not reach here, connectionEnd was able to be retrieved above
   191  		panic("cannot find connection")
   192  	}
   193  
   194  	// expectedCounterpaty is the counterparty of the counterparty's channel end
   195  	// (i.e self)
   196  	expectedCounterparty := types.NewCounterparty(portID, "")
   197  	expectedChannel := types.NewChannel(
   198  		types.INIT, channel.Ordering, expectedCounterparty,
   199  		counterpartyHops, counterpartyVersion,
   200  	)
   201  
   202  	if err := k.connectionKeeper.VerifyChannelState(
   203  		ctx, connectionEnd, proofHeight, proofInit,
   204  		counterparty.PortId, counterparty.ChannelId, expectedChannel,
   205  	); err != nil {
   206  		return "", nil, err
   207  	}
   208  
   209  	var (
   210  		capKey *capabilitytypes.Capability
   211  		err    error
   212  	)
   213  
   214  	if !previousChannelFound {
   215  		capKey, err = k.scopedKeeper.NewCapability(ctx, host.ChannelCapabilityPath(portID, channelID))
   216  		if err != nil {
   217  			return "", nil, sdkerrors.Wrapf(err, "could not create channel capability for port ID %s and channel ID %s", portID, channelID)
   218  		}
   219  
   220  		k.SetNextSequenceSend(ctx, portID, channelID, 1)
   221  		k.SetNextSequenceRecv(ctx, portID, channelID, 1)
   222  		k.SetNextSequenceAck(ctx, portID, channelID, 1)
   223  	} else {
   224  		// capability initialized in ChanOpenInit
   225  		capKey, found = k.scopedKeeper.GetCapability(ctx, host.ChannelCapabilityPath(portID, channelID))
   226  		if !found {
   227  			return "", nil, sdkerrors.Wrapf(types.ErrChannelCapabilityNotFound,
   228  				"capability not found for existing channel, portID (%s) channelID (%s)", portID, channelID,
   229  			)
   230  		}
   231  	}
   232  
   233  	k.SetChannel(ctx, portID, channelID, channel)
   234  
   235  	k.Logger(ctx).Info("channel state updated", "port-id", portID, "channel-id", channelID, "previous-state", previousChannel.State.String(), "new-state", "TRYOPEN")
   236  
   237  	ctx.EventManager().EmitEvents(sdk.Events{
   238  		sdk.NewEvent(
   239  			types.EventTypeChannelOpenTry,
   240  			sdk.NewAttribute(types.AttributeKeyPortID, portID),
   241  			sdk.NewAttribute(types.AttributeKeyChannelID, channelID),
   242  			sdk.NewAttribute(types.AttributeCounterpartyPortID, channel.Counterparty.PortId),
   243  			sdk.NewAttribute(types.AttributeCounterpartyChannelID, channel.Counterparty.ChannelId),
   244  			sdk.NewAttribute(types.AttributeKeyConnectionID, channel.ConnectionHops[0]),
   245  		),
   246  	})
   247  
   248  	return channelID, capKey, nil
   249  }
   250  
   251  // ChanOpenAck is called by the handshake-originating module to acknowledge the
   252  // acceptance of the initial request by the counterparty module on the other chain.
   253  func (k Keeper) ChanOpenAck(
   254  	ctx sdk.Context,
   255  	portID,
   256  	channelID string,
   257  	chanCap *capabilitytypes.Capability,
   258  	counterpartyVersion,
   259  	counterpartyChannelID string,
   260  	proofTry []byte,
   261  	proofHeight exported.Height,
   262  ) error {
   263  	channel, found := k.GetChannel(ctx, portID, channelID)
   264  	if !found {
   265  		return sdkerrors.Wrapf(types.ErrChannelNotFound, "port ID (%s) channel ID (%s)", portID, channelID)
   266  	}
   267  
   268  	if !(channel.State == types.INIT || channel.State == types.TRYOPEN) {
   269  		return sdkerrors.Wrapf(
   270  			types.ErrInvalidChannelState,
   271  			"channel state should be INIT or TRYOPEN (got %s)", channel.State.String(),
   272  		)
   273  	}
   274  
   275  	if !k.scopedKeeper.AuthenticateCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)) {
   276  		return sdkerrors.Wrapf(types.ErrChannelCapabilityNotFound, "caller does not own capability for channel, port ID (%s) channel ID (%s)", portID, channelID)
   277  	}
   278  
   279  	connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0])
   280  	if !found {
   281  		return sdkerrors.Wrap(connectiontypes.ErrConnectionNotFound, channel.ConnectionHops[0])
   282  	}
   283  
   284  	if connectionEnd.GetState() != int32(connectiontypes.OPEN) {
   285  		return sdkerrors.Wrapf(
   286  			connectiontypes.ErrInvalidConnectionState,
   287  			"connection state is not OPEN (got %s)", connectiontypes.State(connectionEnd.GetState()).String(),
   288  		)
   289  	}
   290  
   291  	counterpartyHops, found := k.CounterpartyHops(ctx, channel)
   292  	if !found {
   293  		// should not reach here, connectionEnd was able to be retrieved above
   294  		panic("cannot find connection")
   295  	}
   296  
   297  	// counterparty of the counterparty channel end (i.e self)
   298  	expectedCounterparty := types.NewCounterparty(portID, channelID)
   299  	expectedChannel := types.NewChannel(
   300  		types.TRYOPEN, channel.Ordering, expectedCounterparty,
   301  		counterpartyHops, counterpartyVersion,
   302  	)
   303  
   304  	if err := k.connectionKeeper.VerifyChannelState(
   305  		ctx, connectionEnd, proofHeight, proofTry,
   306  		channel.Counterparty.PortId, counterpartyChannelID,
   307  		expectedChannel,
   308  	); err != nil {
   309  		return err
   310  	}
   311  
   312  	k.Logger(ctx).Info("channel state updated", "port-id", portID, "channel-id", channelID, "previous-state", channel.State.String(), "new-state", "OPEN")
   313  
   314  	channel.State = types.OPEN
   315  	channel.Version = counterpartyVersion
   316  	channel.Counterparty.ChannelId = counterpartyChannelID
   317  	k.SetChannel(ctx, portID, channelID, channel)
   318  
   319  	ctx.EventManager().EmitEvents(sdk.Events{
   320  		sdk.NewEvent(
   321  			types.EventTypeChannelOpenAck,
   322  			sdk.NewAttribute(types.AttributeKeyPortID, portID),
   323  			sdk.NewAttribute(types.AttributeKeyChannelID, channelID),
   324  			sdk.NewAttribute(types.AttributeCounterpartyPortID, channel.Counterparty.PortId),
   325  			sdk.NewAttribute(types.AttributeCounterpartyChannelID, channel.Counterparty.ChannelId),
   326  			sdk.NewAttribute(types.AttributeKeyConnectionID, channel.ConnectionHops[0]),
   327  		),
   328  	})
   329  
   330  	return nil
   331  }
   332  
   333  // ChanOpenConfirm is called by the counterparty module to close their end of the
   334  //
   335  //	channel, since the other end has been closed.
   336  func (k Keeper) ChanOpenConfirm(
   337  	ctx sdk.Context,
   338  	portID,
   339  	channelID string,
   340  	chanCap *capabilitytypes.Capability,
   341  	proofAck []byte,
   342  	proofHeight exported.Height,
   343  ) error {
   344  	channel, found := k.GetChannel(ctx, portID, channelID)
   345  	if !found {
   346  		return sdkerrors.Wrapf(types.ErrChannelNotFound, "port ID (%s) channel ID (%s)", portID, channelID)
   347  	}
   348  
   349  	if channel.State != types.TRYOPEN {
   350  		return sdkerrors.Wrapf(
   351  			types.ErrInvalidChannelState,
   352  			"channel state is not TRYOPEN (got %s)", channel.State.String(),
   353  		)
   354  	}
   355  
   356  	if !k.scopedKeeper.AuthenticateCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)) {
   357  		return sdkerrors.Wrapf(types.ErrChannelCapabilityNotFound, "caller does not own capability for channel, port ID (%s) channel ID (%s)", portID, channelID)
   358  	}
   359  
   360  	connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0])
   361  	if !found {
   362  		return sdkerrors.Wrap(connectiontypes.ErrConnectionNotFound, channel.ConnectionHops[0])
   363  	}
   364  
   365  	if connectionEnd.GetState() != int32(connectiontypes.OPEN) {
   366  		return sdkerrors.Wrapf(
   367  			connectiontypes.ErrInvalidConnectionState,
   368  			"connection state is not OPEN (got %s)", connectiontypes.State(connectionEnd.GetState()).String(),
   369  		)
   370  	}
   371  
   372  	counterpartyHops, found := k.CounterpartyHops(ctx, channel)
   373  	if !found {
   374  		// Should not reach here, connectionEnd was able to be retrieved above
   375  		panic("cannot find connection")
   376  	}
   377  
   378  	counterparty := types.NewCounterparty(portID, channelID)
   379  	expectedChannel := types.NewChannel(
   380  		types.OPEN, channel.Ordering, counterparty,
   381  		counterpartyHops, channel.Version,
   382  	)
   383  
   384  	if err := k.connectionKeeper.VerifyChannelState(
   385  		ctx, connectionEnd, proofHeight, proofAck,
   386  		channel.Counterparty.PortId, channel.Counterparty.ChannelId,
   387  		expectedChannel,
   388  	); err != nil {
   389  		return err
   390  	}
   391  
   392  	channel.State = types.OPEN
   393  	k.SetChannel(ctx, portID, channelID, channel)
   394  	k.Logger(ctx).Info("channel state updated", "port-id", portID, "channel-id", channelID, "previous-state", "TRYOPEN", "new-state", "OPEN")
   395  
   396  	ctx.EventManager().EmitEvents(sdk.Events{
   397  		sdk.NewEvent(
   398  			types.EventTypeChannelOpenConfirm,
   399  			sdk.NewAttribute(types.AttributeKeyPortID, portID),
   400  			sdk.NewAttribute(types.AttributeKeyChannelID, channelID),
   401  			sdk.NewAttribute(types.AttributeCounterpartyPortID, channel.Counterparty.PortId),
   402  			sdk.NewAttribute(types.AttributeCounterpartyChannelID, channel.Counterparty.ChannelId),
   403  			sdk.NewAttribute(types.AttributeKeyConnectionID, channel.ConnectionHops[0]),
   404  		),
   405  	})
   406  
   407  	return nil
   408  }
   409  
   410  // Closing Handshake
   411  //
   412  // This section defines the set of functions required to close a channel handshake
   413  // as defined in https://github.com/cosmos/ics/tree/master/spec/ics-004-channel-and-packet-semantics#closing-handshake
   414  //
   415  // ChanCloseInit is called by either module to close their end of the channel. Once
   416  // closed, channels cannot be reopened.
   417  func (k Keeper) ChanCloseInit(
   418  	ctx sdk.Context,
   419  	portID,
   420  	channelID string,
   421  	chanCap *capabilitytypes.Capability,
   422  ) error {
   423  	if !k.scopedKeeper.AuthenticateCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)) {
   424  		return sdkerrors.Wrapf(types.ErrChannelCapabilityNotFound, "caller does not own capability for channel, port ID (%s) channel ID (%s)", portID, channelID)
   425  	}
   426  
   427  	channel, found := k.GetChannel(ctx, portID, channelID)
   428  	if !found {
   429  		return sdkerrors.Wrapf(types.ErrChannelNotFound, "port ID (%s) channel ID (%s)", portID, channelID)
   430  	}
   431  
   432  	if channel.State == types.CLOSED {
   433  		return sdkerrors.Wrap(types.ErrInvalidChannelState, "channel is already CLOSED")
   434  	}
   435  
   436  	connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0])
   437  	if !found {
   438  		return sdkerrors.Wrap(connectiontypes.ErrConnectionNotFound, channel.ConnectionHops[0])
   439  	}
   440  
   441  	if connectionEnd.GetState() != int32(connectiontypes.OPEN) {
   442  		return sdkerrors.Wrapf(
   443  			connectiontypes.ErrInvalidConnectionState,
   444  			"connection state is not OPEN (got %s)", connectiontypes.State(connectionEnd.GetState()).String(),
   445  		)
   446  	}
   447  
   448  	k.Logger(ctx).Info("channel state updated", "port-id", portID, "channel-id", channelID, "previous-state", channel.State.String(), "new-state", "CLOSED")
   449  
   450  	channel.State = types.CLOSED
   451  	k.SetChannel(ctx, portID, channelID, channel)
   452  
   453  	ctx.EventManager().EmitEvents(sdk.Events{
   454  		sdk.NewEvent(
   455  			types.EventTypeChannelCloseInit,
   456  			sdk.NewAttribute(types.AttributeKeyPortID, portID),
   457  			sdk.NewAttribute(types.AttributeKeyChannelID, channelID),
   458  			sdk.NewAttribute(types.AttributeCounterpartyPortID, channel.Counterparty.PortId),
   459  			sdk.NewAttribute(types.AttributeCounterpartyChannelID, channel.Counterparty.ChannelId),
   460  			sdk.NewAttribute(types.AttributeKeyConnectionID, channel.ConnectionHops[0]),
   461  		),
   462  	})
   463  
   464  	return nil
   465  }
   466  
   467  // ChanCloseConfirm is called by the counterparty module to close their end of the
   468  // channel, since the other end has been closed.
   469  func (k Keeper) ChanCloseConfirm(
   470  	ctx sdk.Context,
   471  	portID,
   472  	channelID string,
   473  	chanCap *capabilitytypes.Capability,
   474  	proofInit []byte,
   475  	proofHeight exported.Height,
   476  ) error {
   477  	if !k.scopedKeeper.AuthenticateCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)) {
   478  		return sdkerrors.Wrap(types.ErrChannelCapabilityNotFound, "caller does not own capability for channel, port ID (%s) channel ID (%s)")
   479  	}
   480  
   481  	channel, found := k.GetChannel(ctx, portID, channelID)
   482  	if !found {
   483  		return sdkerrors.Wrapf(types.ErrChannelNotFound, "port ID (%s) channel ID (%s)", portID, channelID)
   484  	}
   485  
   486  	if channel.State == types.CLOSED {
   487  		return sdkerrors.Wrap(types.ErrInvalidChannelState, "channel is already CLOSED")
   488  	}
   489  
   490  	connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0])
   491  	if !found {
   492  		return sdkerrors.Wrap(connectiontypes.ErrConnectionNotFound, channel.ConnectionHops[0])
   493  	}
   494  
   495  	if connectionEnd.GetState() != int32(connectiontypes.OPEN) {
   496  		return sdkerrors.Wrapf(
   497  			connectiontypes.ErrInvalidConnectionState,
   498  			"connection state is not OPEN (got %s)", connectiontypes.State(connectionEnd.GetState()).String(),
   499  		)
   500  	}
   501  
   502  	counterpartyHops, found := k.CounterpartyHops(ctx, channel)
   503  	if !found {
   504  		// Should not reach here, connectionEnd was able to be retrieved above
   505  		panic("cannot find connection")
   506  	}
   507  
   508  	counterparty := types.NewCounterparty(portID, channelID)
   509  	expectedChannel := types.NewChannel(
   510  		types.CLOSED, channel.Ordering, counterparty,
   511  		counterpartyHops, channel.Version,
   512  	)
   513  
   514  	if err := k.connectionKeeper.VerifyChannelState(
   515  		ctx, connectionEnd, proofHeight, proofInit,
   516  		channel.Counterparty.PortId, channel.Counterparty.ChannelId,
   517  		expectedChannel,
   518  	); err != nil {
   519  		return err
   520  	}
   521  
   522  	k.Logger(ctx).Info("channel state updated", "port-id", portID, "channel-id", channelID, "previous-state", channel.State.String(), "new-state", "CLOSED")
   523  
   524  	channel.State = types.CLOSED
   525  	k.SetChannel(ctx, portID, channelID, channel)
   526  
   527  	ctx.EventManager().EmitEvents(sdk.Events{
   528  		sdk.NewEvent(
   529  			types.EventTypeChannelCloseConfirm,
   530  			sdk.NewAttribute(types.AttributeKeyPortID, portID),
   531  			sdk.NewAttribute(types.AttributeKeyChannelID, channelID),
   532  			sdk.NewAttribute(types.AttributeCounterpartyPortID, channel.Counterparty.PortId),
   533  			sdk.NewAttribute(types.AttributeCounterpartyChannelID, channel.Counterparty.ChannelId),
   534  			sdk.NewAttribute(types.AttributeKeyConnectionID, channel.ConnectionHops[0]),
   535  		),
   536  	})
   537  
   538  	return nil
   539  }