package keeper import ( "fmt" "strings" errorsmod "cosmossdk.io/errors" sdk "git.cw.tr/mukan-network/mukan-sdk/types" icatypes "git.cw.tr/mukan-network/mukan-ibc/modules/apps/27-interchain-accounts/types" connectiontypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/03-connection/types" channeltypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/04-channel/types" ) // OnChanOpenInit performs basic validation of channel initialization. // The counterparty port identifier must be the host chain representation as defined in the types package, // the channel version must be equal to the version in the types package, // there must not be an active channel for the specified port identifier. func (k Keeper) OnChanOpenInit( ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID string, channelID string, counterparty channeltypes.Counterparty, version string, ) (string, error) { if !strings.HasPrefix(portID, icatypes.ControllerPortPrefix) { return "", errorsmod.Wrapf(icatypes.ErrInvalidControllerPort, "expected %s{owner-account-address}, got %s", icatypes.ControllerPortPrefix, portID) } if counterparty.PortId != icatypes.HostPortID { return "", errorsmod.Wrapf(icatypes.ErrInvalidHostPort, "expected %s, got %s", icatypes.HostPortID, counterparty.PortId) } var ( err error metadata icatypes.Metadata ) if strings.TrimSpace(version) == "" { connection, err := k.channelKeeper.GetConnection(ctx, connectionHops[0]) if err != nil { return "", err } metadata = icatypes.NewDefaultMetadata(connectionHops[0], connection.Counterparty.ConnectionId) } else { metadata, err = icatypes.MetadataFromVersion(version) if err != nil { return "", err } } if err := icatypes.ValidateControllerMetadata(ctx, k.channelKeeper, connectionHops, metadata); err != nil { return "", err } activeChannelID, found := k.GetActiveChannelID(ctx, connectionHops[0], portID) if found { channel, found := k.channelKeeper.GetChannel(ctx, portID, activeChannelID) if !found { panic(fmt.Errorf("active channel mapping set for %s but channel does not exist in channel store", activeChannelID)) } if channel.State != channeltypes.CLOSED { return "", errorsmod.Wrapf(icatypes.ErrActiveChannelAlreadySet, "existing active channel %s for portID %s must be %s", activeChannelID, portID, channeltypes.CLOSED) } if channel.Ordering != order { return "", errorsmod.Wrapf(channeltypes.ErrInvalidChannelOrdering, "order cannot change when reopening a channel expected %s, got %s", channel.Ordering, order) } appVersion, found := k.GetAppVersion(ctx, portID, activeChannelID) if !found { panic(fmt.Errorf("active channel mapping set for %s, but channel does not exist in channel store", activeChannelID)) } if !icatypes.IsPreviousMetadataEqual(appVersion, metadata) { return "", errorsmod.Wrap(icatypes.ErrInvalidVersion, "previous active channel metadata does not match provided version") } } return string(icatypes.ModuleCdc.MustMarshalJSON(&metadata)), nil } // OnChanOpenAck sets the active channel for the interchain account/owner pair // and stores the associated interchain account address in state keyed by it's corresponding port identifier func (k Keeper) OnChanOpenAck( ctx sdk.Context, portID, channelID string, counterpartyVersion string, ) error { if portID == icatypes.HostPortID { return errorsmod.Wrapf(icatypes.ErrInvalidControllerPort, "portID cannot be host chain port ID: %s", icatypes.HostPortID) } if !strings.HasPrefix(portID, icatypes.ControllerPortPrefix) { return errorsmod.Wrapf(icatypes.ErrInvalidControllerPort, "expected %s{owner-account-address}, got %s", icatypes.ControllerPortPrefix, portID) } metadata, err := icatypes.MetadataFromVersion(counterpartyVersion) if err != nil { return err } if activeChannelID, found := k.GetOpenActiveChannel(ctx, metadata.ControllerConnectionId, portID); found { return errorsmod.Wrapf(icatypes.ErrActiveChannelAlreadySet, "existing active channel %s for portID %s", activeChannelID, portID) } channel, found := k.channelKeeper.GetChannel(ctx, portID, channelID) if !found { return errorsmod.Wrapf(channeltypes.ErrChannelNotFound, "failed to retrieve channel %s on port %s", channelID, portID) } if err := icatypes.ValidateControllerMetadata(ctx, k.channelKeeper, channel.ConnectionHops, metadata); err != nil { return err } if strings.TrimSpace(metadata.Address) == "" { return errorsmod.Wrap(icatypes.ErrInvalidAccountAddress, "interchain account address cannot be empty") } k.SetActiveChannelID(ctx, metadata.ControllerConnectionId, portID, channelID) k.SetInterchainAccountAddress(ctx, metadata.ControllerConnectionId, portID, metadata.Address) return nil } // OnChanCloseConfirm removes the active channel stored in state func (Keeper) OnChanCloseConfirm( ctx sdk.Context, portID, channelID string, ) error { return nil } // OnChanUpgradeInit performs the upgrade init step of the channel upgrade handshake. // The upgrade init callback must verify the proposed changes to the order, connectionHops, and version. // Within the version we have the tx type, encoding, interchain account address, host/controller connectionID's // and the ICS27 protocol version. // // The following may be changed: // - tx type (must be supported) // - encoding (must be supported) // - order // // The following may not be changed: // - connectionHops (and subsequently host/controller connectionIDs) // - interchain account address // - ICS27 protocol version func (k Keeper) OnChanUpgradeInit(ctx sdk.Context, portID, channelID string, proposedOrder channeltypes.Order, proposedConnectionHops []string, proposedversion string) (string, error) { // verify connection hops has not changed connectionID, err := k.GetConnectionID(ctx, portID, channelID) if err != nil { return "", err } if len(proposedConnectionHops) != 1 || proposedConnectionHops[0] != connectionID { return "", errorsmod.Wrapf(channeltypes.ErrInvalidUpgrade, "expected connection hops %s, got %s", []string{connectionID}, proposedConnectionHops) } // verify proposed version only modifies tx type or encoding if strings.TrimSpace(proposedversion) == "" { return "", errorsmod.Wrap(icatypes.ErrInvalidVersion, "version cannot be empty") } proposedMetadata, err := icatypes.MetadataFromVersion(proposedversion) if err != nil { return "", err } currentMetadata, err := k.getAppMetadata(ctx, portID, channelID) if err != nil { return "", err } // ValidateControllerMetadata will ensure the ICS27 protocol version has not changed and that the // tx type and encoding are supported if err := icatypes.ValidateControllerMetadata(ctx, k.channelKeeper, proposedConnectionHops, proposedMetadata); err != nil { return "", errorsmod.Wrap(err, "invalid upgrade metadata") } // the interchain account address on the host chain // must remain the same after the upgrade. if currentMetadata.Address != proposedMetadata.Address { return "", errorsmod.Wrap(icatypes.ErrInvalidAccountAddress, "interchain account address cannot be changed") } if currentMetadata.ControllerConnectionId != proposedMetadata.ControllerConnectionId { return "", errorsmod.Wrap(connectiontypes.ErrInvalidConnection, "proposed controller connection ID must not change") } if currentMetadata.HostConnectionId != proposedMetadata.HostConnectionId { return "", errorsmod.Wrap(connectiontypes.ErrInvalidConnection, "proposed host connection ID must not change") } return proposedversion, nil } // OnChanUpgradeAck implements the ack setup of the channel upgrade handshake. // The upgrade ack callback must verify the proposed changes to the channel version. // Within the channel version we have the tx type, encoding, interchain account address, host/controller connectionID's // and the ICS27 protocol version. // // The following may be changed: // - tx type (must be supported) // - encoding (must be supported) // // The following may not be changed: // - controller connectionID // - host connectionID // - interchain account address // - ICS27 protocol version func (k Keeper) OnChanUpgradeAck(ctx sdk.Context, portID, channelID, counterpartyVersion string) error { if strings.TrimSpace(counterpartyVersion) == "" { return errorsmod.Wrap(channeltypes.ErrInvalidChannelVersion, "counterparty version cannot be empty") } proposedMetadata, err := icatypes.MetadataFromVersion(counterpartyVersion) if err != nil { return err } currentMetadata, err := k.getAppMetadata(ctx, portID, channelID) if err != nil { return err } channel, found := k.channelKeeper.GetChannel(ctx, portID, channelID) if !found { return errorsmod.Wrapf(channeltypes.ErrChannelNotFound, "failed to retrieve channel %s on port %s", channelID, portID) } // ValidateControllerMetadata will ensure the ICS27 protocol version has not changed and that the // tx type and encoding are supported. Note, we pass in the current channel connection hops. The upgrade init // step will verify that the proposed connection hops will not change. if err := icatypes.ValidateControllerMetadata(ctx, k.channelKeeper, channel.ConnectionHops, proposedMetadata); err != nil { return errorsmod.Wrap(err, "invalid upgrade metadata") } // the interchain account address on the host chain // must remain the same after the upgrade. if currentMetadata.Address != proposedMetadata.Address { return errorsmod.Wrap(icatypes.ErrInvalidAccountAddress, "address cannot be changed") } if currentMetadata.ControllerConnectionId != proposedMetadata.ControllerConnectionId { return errorsmod.Wrap(connectiontypes.ErrInvalidConnection, "proposed controller connection ID must not change") } if currentMetadata.HostConnectionId != proposedMetadata.HostConnectionId { return errorsmod.Wrap(connectiontypes.ErrInvalidConnection, "proposed host connection ID must not change") } return nil }