github.com/okex/exchain@v1.8.0/libs/ibc-go/modules/core/04-channel/keeper/handshake.go (about)

     1  package keeper
     2  
     3  import (
     4  	sdk "github.com/okex/exchain/libs/cosmos-sdk/types"
     5  	sdkerrors "github.com/okex/exchain/libs/cosmos-sdk/types/errors"
     6  	capabilitytypes "github.com/okex/exchain/libs/cosmos-sdk/x/capability/types"
     7  	connectiontypes "github.com/okex/exchain/libs/ibc-go/modules/core/03-connection/types"
     8  	"github.com/okex/exchain/libs/ibc-go/modules/core/04-channel/types"
     9  	porttypes "github.com/okex/exchain/libs/ibc-go/modules/core/05-port/types"
    10  	host "github.com/okex/exchain/libs/ibc-go/modules/core/24-host"
    11  	"github.com/okex/exchain/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  //  channel, since the other end has been closed.
   335  func (k Keeper) ChanOpenConfirm(
   336  	ctx sdk.Context,
   337  	portID,
   338  	channelID string,
   339  	chanCap *capabilitytypes.Capability,
   340  	proofAck []byte,
   341  	proofHeight exported.Height,
   342  ) error {
   343  	channel, found := k.GetChannel(ctx, portID, channelID)
   344  	if !found {
   345  		return sdkerrors.Wrapf(types.ErrChannelNotFound, "port ID (%s) channel ID (%s)", portID, channelID)
   346  	}
   347  
   348  	if channel.State != types.TRYOPEN {
   349  		return sdkerrors.Wrapf(
   350  			types.ErrInvalidChannelState,
   351  			"channel state is not TRYOPEN (got %s)", channel.State.String(),
   352  		)
   353  	}
   354  
   355  	if !k.scopedKeeper.AuthenticateCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)) {
   356  		return sdkerrors.Wrapf(types.ErrChannelCapabilityNotFound, "caller does not own capability for channel, port ID (%s) channel ID (%s)", portID, channelID)
   357  	}
   358  
   359  	connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0])
   360  	if !found {
   361  		return sdkerrors.Wrap(connectiontypes.ErrConnectionNotFound, channel.ConnectionHops[0])
   362  	}
   363  
   364  	if connectionEnd.GetState() != int32(connectiontypes.OPEN) {
   365  		return sdkerrors.Wrapf(
   366  			connectiontypes.ErrInvalidConnectionState,
   367  			"connection state is not OPEN (got %s)", connectiontypes.State(connectionEnd.GetState()).String(),
   368  		)
   369  	}
   370  
   371  	counterpartyHops, found := k.CounterpartyHops(ctx, channel)
   372  	if !found {
   373  		// Should not reach here, connectionEnd was able to be retrieved above
   374  		panic("cannot find connection")
   375  	}
   376  
   377  	counterparty := types.NewCounterparty(portID, channelID)
   378  	expectedChannel := types.NewChannel(
   379  		types.OPEN, channel.Ordering, counterparty,
   380  		counterpartyHops, channel.Version,
   381  	)
   382  
   383  	if err := k.connectionKeeper.VerifyChannelState(
   384  		ctx, connectionEnd, proofHeight, proofAck,
   385  		channel.Counterparty.PortId, channel.Counterparty.ChannelId,
   386  		expectedChannel,
   387  	); err != nil {
   388  		return err
   389  	}
   390  
   391  	channel.State = types.OPEN
   392  	k.SetChannel(ctx, portID, channelID, channel)
   393  	k.Logger(ctx).Info("channel state updated", "port-id", portID, "channel-id", channelID, "previous-state", "TRYOPEN", "new-state", "OPEN")
   394  
   395  	ctx.EventManager().EmitEvents(sdk.Events{
   396  		sdk.NewEvent(
   397  			types.EventTypeChannelOpenConfirm,
   398  			sdk.NewAttribute(types.AttributeKeyPortID, portID),
   399  			sdk.NewAttribute(types.AttributeKeyChannelID, channelID),
   400  			sdk.NewAttribute(types.AttributeCounterpartyPortID, channel.Counterparty.PortId),
   401  			sdk.NewAttribute(types.AttributeCounterpartyChannelID, channel.Counterparty.ChannelId),
   402  			sdk.NewAttribute(types.AttributeKeyConnectionID, channel.ConnectionHops[0]),
   403  		),
   404  	})
   405  
   406  	return nil
   407  }
   408  
   409  // Closing Handshake
   410  //
   411  // This section defines the set of functions required to close a channel handshake
   412  // as defined in https://github.com/cosmos/ics/tree/master/spec/ics-004-channel-and-packet-semantics#closing-handshake
   413  //
   414  // ChanCloseInit is called by either module to close their end of the channel. Once
   415  // closed, channels cannot be reopened.
   416  func (k Keeper) ChanCloseInit(
   417  	ctx sdk.Context,
   418  	portID,
   419  	channelID string,
   420  	chanCap *capabilitytypes.Capability,
   421  ) error {
   422  	if !k.scopedKeeper.AuthenticateCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)) {
   423  		return sdkerrors.Wrapf(types.ErrChannelCapabilityNotFound, "caller does not own capability for channel, port ID (%s) channel ID (%s)", portID, channelID)
   424  	}
   425  
   426  	channel, found := k.GetChannel(ctx, portID, channelID)
   427  	if !found {
   428  		return sdkerrors.Wrapf(types.ErrChannelNotFound, "port ID (%s) channel ID (%s)", portID, channelID)
   429  	}
   430  
   431  	if channel.State == types.CLOSED {
   432  		return sdkerrors.Wrap(types.ErrInvalidChannelState, "channel is already CLOSED")
   433  	}
   434  
   435  	connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0])
   436  	if !found {
   437  		return sdkerrors.Wrap(connectiontypes.ErrConnectionNotFound, channel.ConnectionHops[0])
   438  	}
   439  
   440  	if connectionEnd.GetState() != int32(connectiontypes.OPEN) {
   441  		return sdkerrors.Wrapf(
   442  			connectiontypes.ErrInvalidConnectionState,
   443  			"connection state is not OPEN (got %s)", connectiontypes.State(connectionEnd.GetState()).String(),
   444  		)
   445  	}
   446  
   447  	k.Logger(ctx).Info("channel state updated", "port-id", portID, "channel-id", channelID, "previous-state", channel.State.String(), "new-state", "CLOSED")
   448  
   449  	channel.State = types.CLOSED
   450  	k.SetChannel(ctx, portID, channelID, channel)
   451  
   452  	ctx.EventManager().EmitEvents(sdk.Events{
   453  		sdk.NewEvent(
   454  			types.EventTypeChannelCloseInit,
   455  			sdk.NewAttribute(types.AttributeKeyPortID, portID),
   456  			sdk.NewAttribute(types.AttributeKeyChannelID, channelID),
   457  			sdk.NewAttribute(types.AttributeCounterpartyPortID, channel.Counterparty.PortId),
   458  			sdk.NewAttribute(types.AttributeCounterpartyChannelID, channel.Counterparty.ChannelId),
   459  			sdk.NewAttribute(types.AttributeKeyConnectionID, channel.ConnectionHops[0]),
   460  		),
   461  	})
   462  
   463  	return nil
   464  }
   465  
   466  // ChanCloseConfirm is called by the counterparty module to close their end of the
   467  // channel, since the other end has been closed.
   468  func (k Keeper) ChanCloseConfirm(
   469  	ctx sdk.Context,
   470  	portID,
   471  	channelID string,
   472  	chanCap *capabilitytypes.Capability,
   473  	proofInit []byte,
   474  	proofHeight exported.Height,
   475  ) error {
   476  	if !k.scopedKeeper.AuthenticateCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)) {
   477  		return sdkerrors.Wrap(types.ErrChannelCapabilityNotFound, "caller does not own capability for channel, port ID (%s) channel ID (%s)")
   478  	}
   479  
   480  	channel, found := k.GetChannel(ctx, portID, channelID)
   481  	if !found {
   482  		return sdkerrors.Wrapf(types.ErrChannelNotFound, "port ID (%s) channel ID (%s)", portID, channelID)
   483  	}
   484  
   485  	if channel.State == types.CLOSED {
   486  		return sdkerrors.Wrap(types.ErrInvalidChannelState, "channel is already CLOSED")
   487  	}
   488  
   489  	connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0])
   490  	if !found {
   491  		return sdkerrors.Wrap(connectiontypes.ErrConnectionNotFound, channel.ConnectionHops[0])
   492  	}
   493  
   494  	if connectionEnd.GetState() != int32(connectiontypes.OPEN) {
   495  		return sdkerrors.Wrapf(
   496  			connectiontypes.ErrInvalidConnectionState,
   497  			"connection state is not OPEN (got %s)", connectiontypes.State(connectionEnd.GetState()).String(),
   498  		)
   499  	}
   500  
   501  	counterpartyHops, found := k.CounterpartyHops(ctx, channel)
   502  	if !found {
   503  		// Should not reach here, connectionEnd was able to be retrieved above
   504  		panic("cannot find connection")
   505  	}
   506  
   507  	counterparty := types.NewCounterparty(portID, channelID)
   508  	expectedChannel := types.NewChannel(
   509  		types.CLOSED, channel.Ordering, counterparty,
   510  		counterpartyHops, channel.Version,
   511  	)
   512  
   513  	if err := k.connectionKeeper.VerifyChannelState(
   514  		ctx, connectionEnd, proofHeight, proofInit,
   515  		channel.Counterparty.PortId, channel.Counterparty.ChannelId,
   516  		expectedChannel,
   517  	); err != nil {
   518  		return err
   519  	}
   520  
   521  	k.Logger(ctx).Info("channel state updated", "port-id", portID, "channel-id", channelID, "previous-state", channel.State.String(), "new-state", "CLOSED")
   522  
   523  	channel.State = types.CLOSED
   524  	k.SetChannel(ctx, portID, channelID, channel)
   525  
   526  	ctx.EventManager().EmitEvents(sdk.Events{
   527  		sdk.NewEvent(
   528  			types.EventTypeChannelCloseConfirm,
   529  			sdk.NewAttribute(types.AttributeKeyPortID, portID),
   530  			sdk.NewAttribute(types.AttributeKeyChannelID, channelID),
   531  			sdk.NewAttribute(types.AttributeCounterpartyPortID, channel.Counterparty.PortId),
   532  			sdk.NewAttribute(types.AttributeCounterpartyChannelID, channel.Counterparty.ChannelId),
   533  			sdk.NewAttribute(types.AttributeKeyConnectionID, channel.ConnectionHops[0]),
   534  		),
   535  	})
   536  
   537  	return nil
   538  }