package keeper import ( "bytes" "fmt" "time" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/capability" client "github.com/cosmos/cosmos-sdk/x/ibc/02-client" connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection" connectionexported "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/exported" "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/exported" "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types" commitmentexported "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/exported" ibctypes "github.com/cosmos/cosmos-sdk/x/ibc/types" ) // SendPacket is called by a module in order to send an IBC packet on a channel // end owned by the calling module to the corresponding module on the counterparty // chain. func (k Keeper) SendPacket( ctx sdk.Context, channelCap *capability.Capability, packet exported.PacketI, ) error { if err := packet.ValidateBasic(); err != nil { return err } channel, found := k.GetChannel(ctx, packet.GetSourcePort(), packet.GetSourceChannel()) if !found { return sdkerrors.Wrap(types.ErrChannelNotFound, packet.GetSourceChannel()) } if channel.State == exported.CLOSED { return sdkerrors.Wrapf( types.ErrInvalidChannelState, "channel is CLOSED (got %s)", channel.State.String(), ) } if !k.scopedKeeper.AuthenticateCapability(ctx, channelCap, ibctypes.ChannelCapabilityPath(packet.GetSourcePort(), packet.GetSourceChannel())) { return sdkerrors.Wrap(types.ErrChannelCapabilityNotFound, "caller does not own capability for channel") } if packet.GetDestPort() != channel.Counterparty.PortID { return sdkerrors.Wrapf( types.ErrInvalidPacket, "packet destination port doesn't match the counterparty's port (%s ≠ %s)", packet.GetDestPort(), channel.Counterparty.PortID, ) } if packet.GetDestChannel() != channel.Counterparty.ChannelID { return sdkerrors.Wrapf( types.ErrInvalidPacket, "packet destination channel doesn't match the counterparty's channel (%s ≠ %s)", packet.GetDestChannel(), channel.Counterparty.ChannelID, ) } connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) if !found { return sdkerrors.Wrap(connection.ErrConnectionNotFound, channel.ConnectionHops[0]) } // NOTE: assume UNINITIALIZED is a closed connection if connectionEnd.GetState() == connectionexported.UNINITIALIZED { return sdkerrors.Wrap( connection.ErrInvalidConnectionState, "connection is closed (i.e NONE)", ) } clientState, found := k.clientKeeper.GetClientState(ctx, connectionEnd.GetClientID()) if !found { return client.ErrConsensusStateNotFound } // check if packet timeouted on the receiving chain latestHeight := clientState.GetLatestHeight() if packet.GetTimeoutHeight() != 0 && latestHeight >= packet.GetTimeoutHeight() { return sdkerrors.Wrapf( types.ErrPacketTimeout, "receiving chain block height >= packet timeout height (%d >= %d)", latestHeight, packet.GetTimeoutHeight(), ) } latestTimestamp, err := k.connectionKeeper.GetTimestampAtHeight(ctx, connectionEnd, latestHeight) if err != nil { return err } if packet.GetTimeoutTimestamp() != 0 && latestTimestamp >= packet.GetTimeoutTimestamp() { return sdkerrors.Wrapf( types.ErrPacketTimeout, "receiving chain block timestamp >= packet timeout timestamp (%s >= %s)", time.Unix(0, int64(latestTimestamp)), time.Unix(0, int64(packet.GetTimeoutTimestamp())), ) } nextSequenceSend, found := k.GetNextSequenceSend(ctx, packet.GetSourcePort(), packet.GetSourceChannel()) if !found { return types.ErrSequenceSendNotFound } if packet.GetSequence() != nextSequenceSend { return sdkerrors.Wrapf( types.ErrInvalidPacket, "packet sequence ≠ next send sequence (%d ≠ %d)", packet.GetSequence(), nextSequenceSend, ) } nextSequenceSend++ k.SetNextSequenceSend(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), nextSequenceSend) k.SetPacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence(), types.CommitPacket(packet)) // Emit Event with Packet data along with other packet information for relayer to pick up // and relay to other chain ctx.EventManager().EmitEvents(sdk.Events{ sdk.NewEvent( types.EventTypeSendPacket, sdk.NewAttribute(types.AttributeKeyData, string(packet.GetData())), sdk.NewAttribute(types.AttributeKeyTimeoutHeight, fmt.Sprintf("%d", packet.GetTimeoutHeight())), sdk.NewAttribute(types.AttributeKeyTimeoutTimestamp, fmt.Sprintf("%d", packet.GetTimeoutTimestamp())), sdk.NewAttribute(types.AttributeKeySequence, fmt.Sprintf("%d", packet.GetSequence())), sdk.NewAttribute(types.AttributeKeySrcPort, packet.GetSourcePort()), sdk.NewAttribute(types.AttributeKeySrcChannel, packet.GetSourceChannel()), sdk.NewAttribute(types.AttributeKeyDstPort, packet.GetDestPort()), sdk.NewAttribute(types.AttributeKeyDstChannel, packet.GetDestChannel()), ), }) k.Logger(ctx).Info(fmt.Sprintf("packet sent: %v", packet)) return nil } // RecvPacket is called by a module in order to receive & process an IBC packet // sent on the corresponding channel end on the counterparty chain. func (k Keeper) RecvPacket( ctx sdk.Context, packet exported.PacketI, proof commitmentexported.Proof, proofHeight uint64, ) (exported.PacketI, error) { channel, found := k.GetChannel(ctx, packet.GetDestPort(), packet.GetDestChannel()) if !found { return nil, sdkerrors.Wrap(types.ErrChannelNotFound, packet.GetDestChannel()) } if channel.State != exported.OPEN { return nil, sdkerrors.Wrapf( types.ErrInvalidChannelState, "channel state is not OPEN (got %s)", channel.State.String(), ) } // NOTE: RecvPacket is called by the AnteHandler which acts upon the packet.Route(), // so the capability authentication can be omitted here // packet must come from the channel's counterparty if packet.GetSourcePort() != channel.Counterparty.PortID { return nil, sdkerrors.Wrapf( types.ErrInvalidPacket, "packet source port doesn't match the counterparty's port (%s ≠ %s)", packet.GetSourcePort(), channel.Counterparty.PortID, ) } if packet.GetSourceChannel() != channel.Counterparty.ChannelID { return nil, sdkerrors.Wrapf( types.ErrInvalidPacket, "packet source channel doesn't match the counterparty's channel (%s ≠ %s)", packet.GetSourceChannel(), channel.Counterparty.ChannelID, ) } connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) if !found { return nil, sdkerrors.Wrap(connection.ErrConnectionNotFound, channel.ConnectionHops[0]) } if connectionEnd.GetState() != connectionexported.OPEN { return nil, sdkerrors.Wrapf( connection.ErrInvalidConnectionState, "connection state is not OPEN (got %s)", connectionEnd.GetState().String(), ) } // check if packet timeouted by comparing it with the latest height of the chain if packet.GetTimeoutHeight() != 0 && uint64(ctx.BlockHeight()) >= packet.GetTimeoutHeight() { return nil, sdkerrors.Wrapf( types.ErrPacketTimeout, "block height >= packet timeout height (%d >= %d)", uint64(ctx.BlockHeight()), packet.GetTimeoutHeight(), ) } // check if packet timeouted by comparing it with the latest timestamp of the chain if packet.GetTimeoutTimestamp() != 0 && uint64(ctx.BlockTime().UnixNano()) >= packet.GetTimeoutTimestamp() { return nil, sdkerrors.Wrapf( types.ErrPacketTimeout, "block timestamp >= packet timeout timestamp (%s >= %s)", ctx.BlockTime(), time.Unix(0, int64(packet.GetTimeoutTimestamp())), ) } if err := k.connectionKeeper.VerifyPacketCommitment( ctx, connectionEnd, proofHeight, proof, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence(), types.CommitPacket(packet), ); err != nil { return nil, sdkerrors.Wrap(err, "couldn't verify counterparty packet commitment") } return packet, nil } // PacketExecuted writes the packet execution acknowledgement to the state, // which will be verified by the counterparty chain using AcknowledgePacket. // CONTRACT: each packet handler function should call WriteAcknowledgement at the end of the execution func (k Keeper) PacketExecuted( ctx sdk.Context, chanCap *capability.Capability, packet exported.PacketI, acknowledgement []byte, ) error { channel, found := k.GetChannel(ctx, packet.GetDestPort(), packet.GetDestChannel()) if !found { return sdkerrors.Wrapf(types.ErrChannelNotFound, packet.GetDestChannel()) } // sanity check if channel.State != exported.OPEN { return sdkerrors.Wrapf( types.ErrInvalidChannelState, "channel state is not OPEN (got %s)", channel.State.String(), ) } capName := ibctypes.ChannelCapabilityPath(packet.GetDestPort(), packet.GetDestChannel()) if !k.scopedKeeper.AuthenticateCapability(ctx, chanCap, capName) { return sdkerrors.Wrap(types.ErrInvalidChannelCapability, "channel capability failed authentication") } if acknowledgement != nil || channel.Ordering == exported.UNORDERED { k.SetPacketAcknowledgement( ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), types.CommitAcknowledgement(acknowledgement), ) } if channel.Ordering == exported.ORDERED { nextSequenceRecv, found := k.GetNextSequenceRecv(ctx, packet.GetDestPort(), packet.GetDestChannel()) if !found { return types.ErrSequenceReceiveNotFound } if packet.GetSequence() != nextSequenceRecv { return sdkerrors.Wrapf( types.ErrInvalidPacket, "packet sequence ≠ next receive sequence (%d ≠ %d)", packet.GetSequence(), nextSequenceRecv, ) } nextSequenceRecv++ k.SetNextSequenceRecv(ctx, packet.GetDestPort(), packet.GetDestChannel(), nextSequenceRecv) } // log that a packet has been received & executed k.Logger(ctx).Info(fmt.Sprintf("packet received & executed: %v", packet)) // emit an event that the relayer can query for ctx.EventManager().EmitEvents(sdk.Events{ sdk.NewEvent( types.EventTypeRecvPacket, sdk.NewAttribute(types.AttributeKeyData, string(packet.GetData())), sdk.NewAttribute(types.AttributeKeyAck, string(acknowledgement)), sdk.NewAttribute(types.AttributeKeyTimeoutHeight, fmt.Sprintf("%d", packet.GetTimeoutHeight())), sdk.NewAttribute(types.AttributeKeyTimeoutTimestamp, fmt.Sprintf("%d", packet.GetTimeoutTimestamp())), sdk.NewAttribute(types.AttributeKeySequence, fmt.Sprintf("%d", packet.GetSequence())), sdk.NewAttribute(types.AttributeKeySrcPort, packet.GetSourcePort()), sdk.NewAttribute(types.AttributeKeySrcChannel, packet.GetSourceChannel()), sdk.NewAttribute(types.AttributeKeyDstPort, packet.GetDestPort()), sdk.NewAttribute(types.AttributeKeyDstChannel, packet.GetDestChannel()), ), }) return nil } // AcknowledgePacket is called by a module to process the acknowledgement of a // packet previously sent by the calling module on a channel to a counterparty // module on the counterparty chain. acknowledgePacket also cleans up the packet // commitment, which is no longer necessary since the packet has been received // and acted upon. func (k Keeper) AcknowledgePacket( ctx sdk.Context, packet exported.PacketI, acknowledgement []byte, proof commitmentexported.Proof, proofHeight uint64, ) (exported.PacketI, error) { channel, found := k.GetChannel(ctx, packet.GetSourcePort(), packet.GetSourceChannel()) if !found { return nil, sdkerrors.Wrap(types.ErrChannelNotFound, packet.GetSourceChannel()) } if channel.State != exported.OPEN { return nil, sdkerrors.Wrapf( types.ErrInvalidChannelState, "channel state is not OPEN (got %s)", channel.State.String(), ) } // NOTE: RecvPacket is called by the AnteHandler which acts upon the packet.Route(), // so the capability authentication can be omitted here // packet must have been sent to the channel's counterparty if packet.GetDestPort() != channel.Counterparty.PortID { return nil, sdkerrors.Wrapf( types.ErrInvalidPacket, "packet destination port doesn't match the counterparty's port (%s ≠ %s)", packet.GetDestPort(), channel.Counterparty.PortID, ) } if packet.GetDestChannel() != channel.Counterparty.ChannelID { return nil, sdkerrors.Wrapf( types.ErrInvalidPacket, "packet destination channel doesn't match the counterparty's channel (%s ≠ %s)", packet.GetDestChannel(), channel.Counterparty.ChannelID, ) } connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) if !found { return nil, sdkerrors.Wrap(connection.ErrConnectionNotFound, channel.ConnectionHops[0]) } if connectionEnd.GetState() != connectionexported.OPEN { return nil, sdkerrors.Wrapf( connection.ErrInvalidConnectionState, "connection state is not OPEN (got %s)", connectionEnd.GetState().String(), ) } commitment := k.GetPacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) // verify we sent the packet and haven't cleared it out yet if !bytes.Equal(commitment, types.CommitPacket(packet)) { return nil, sdkerrors.Wrap(types.ErrInvalidPacket, "packet hasn't been sent") } if err := k.connectionKeeper.VerifyPacketAcknowledgement( ctx, connectionEnd, proofHeight, proof, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), acknowledgement, ); err != nil { return nil, sdkerrors.Wrap(err, "invalid acknowledgement on counterparty chain") } // log that a packet has been acknowledged k.Logger(ctx).Info(fmt.Sprintf("packet acknowledged: %v", packet)) // emit an event marking that we have processed the acknowledgement ctx.EventManager().EmitEvents(sdk.Events{ sdk.NewEvent( types.EventTypeAcknowledgePacket, sdk.NewAttribute(types.AttributeKeyTimeoutHeight, fmt.Sprintf("%d", packet.GetTimeoutHeight())), sdk.NewAttribute(types.AttributeKeyTimeoutTimestamp, fmt.Sprintf("%d", packet.GetTimeoutTimestamp())), sdk.NewAttribute(types.AttributeKeySequence, fmt.Sprintf("%d", packet.GetSequence())), sdk.NewAttribute(types.AttributeKeySrcPort, packet.GetSourcePort()), sdk.NewAttribute(types.AttributeKeySrcChannel, packet.GetSourceChannel()), sdk.NewAttribute(types.AttributeKeyDstPort, packet.GetDestPort()), sdk.NewAttribute(types.AttributeKeyDstChannel, packet.GetDestChannel()), ), }) return packet, nil } // CleanupPacket is called by a module to remove a received packet commitment // from storage. The receiving end must have already processed the packet // (whether regularly or past timeout). // // In the ORDERED channel case, CleanupPacket cleans-up a packet on an ordered // channel by proving that the packet has been received on the other end. // // In the UNORDERED channel case, CleanupPacket cleans-up a packet on an // unordered channel by proving that the associated acknowledgement has been //written. func (k Keeper) CleanupPacket( ctx sdk.Context, packet exported.PacketI, proof commitmentexported.Proof, proofHeight, nextSequenceRecv uint64, acknowledgement []byte, ) (exported.PacketI, error) { channel, found := k.GetChannel(ctx, packet.GetSourcePort(), packet.GetSourceChannel()) if !found { return nil, sdkerrors.Wrap(types.ErrChannelNotFound, packet.GetSourceChannel()) } if channel.State != exported.OPEN { return nil, sdkerrors.Wrapf( types.ErrInvalidChannelState, "channel state is not OPEN (got %s)", channel.State.String(), ) } // TODO: blocked by #5542 // capKey, found := k.GetChannelCapability(ctx, packet.GetSourcePort(), packet.GetSourceChannel()) // if !found { // return nil, types.ErrChannelCapabilityNotFound // } // portCapabilityKey := sdk.NewKVStoreKey(capKey) // if !k.portKeeper.Authenticate(portCapabilityKey, packet.GetSourcePort()) { // return nil, sdkerrors.Wrapf(port.ErrInvalidPort, "invalid source port: %s", packet.GetSourcePort()) // } if packet.GetDestPort() != channel.Counterparty.PortID { return nil, sdkerrors.Wrapf(types.ErrInvalidPacket, "packet destination port doesn't match the counterparty's port (%s ≠ %s)", packet.GetDestPort(), channel.Counterparty.PortID, ) } if packet.GetDestChannel() != channel.Counterparty.ChannelID { return nil, sdkerrors.Wrapf( types.ErrInvalidPacket, "packet destination channel doesn't match the counterparty's channel (%s ≠ %s)", packet.GetDestChannel(), channel.Counterparty.ChannelID, ) } connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) if !found { return nil, sdkerrors.Wrap(connection.ErrConnectionNotFound, channel.ConnectionHops[0]) } // check that packet has been received on the other end if nextSequenceRecv <= packet.GetSequence() { return nil, sdkerrors.Wrap(types.ErrInvalidPacket, "packet already received") } commitment := k.GetPacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) // verify we sent the packet and haven't cleared it out yet if !bytes.Equal(commitment, types.CommitPacket(packet)) { return nil, sdkerrors.Wrap(types.ErrInvalidPacket, "packet hasn't been sent") } var err error switch channel.Ordering { case exported.ORDERED: // check that the recv sequence is as claimed err = k.connectionKeeper.VerifyNextSequenceRecv( ctx, connectionEnd, proofHeight, proof, packet.GetDestPort(), packet.GetDestChannel(), nextSequenceRecv, ) case exported.UNORDERED: err = k.connectionKeeper.VerifyPacketAcknowledgement( ctx, connectionEnd, proofHeight, proof, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), acknowledgement, ) default: panic(sdkerrors.Wrapf(types.ErrInvalidChannelOrdering, channel.Ordering.String())) } if err != nil { return nil, sdkerrors.Wrap(err, "packet verification failed") } k.deletePacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) // log that a packet has been acknowledged k.Logger(ctx).Info(fmt.Sprintf("packet cleaned-up: %v", packet)) // emit an event marking that we have cleaned up the packet ctx.EventManager().EmitEvents(sdk.Events{ sdk.NewEvent( types.EventTypeCleanupPacket, sdk.NewAttribute(types.AttributeKeyTimeoutHeight, fmt.Sprintf("%d", packet.GetTimeoutHeight())), sdk.NewAttribute(types.AttributeKeyTimeoutTimestamp, fmt.Sprintf("%d", packet.GetTimeoutTimestamp())), sdk.NewAttribute(types.AttributeKeySequence, fmt.Sprintf("%d", packet.GetSequence())), sdk.NewAttribute(types.AttributeKeySrcPort, packet.GetSourcePort()), sdk.NewAttribute(types.AttributeKeySrcChannel, packet.GetSourceChannel()), sdk.NewAttribute(types.AttributeKeyDstPort, packet.GetDestPort()), sdk.NewAttribute(types.AttributeKeyDstChannel, packet.GetDestChannel()), ), }) return packet, nil }