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" "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/exported" "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types" host "github.com/cosmos/cosmos-sdk/x/ibc/24-host" ) // 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 == types.CLOSED { return sdkerrors.Wrapf( types.ErrInvalidChannelState, "channel is CLOSED (got %s)", channel.State.String(), ) } if !k.scopedKeeper.AuthenticateCapability(ctx, channelCap, host.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() == int32(connection.UNINITIALIZED) { return sdkerrors.Wrap( connection.ErrInvalidConnectionState, "connection is UNINITIALIZED", ) } 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 sdkerrors.Wrapf( types.ErrSequenceSendNotFound, "source port: %s, source channel: %s", packet.GetSourcePort(), packet.GetSourceChannel(), ) } 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 []byte, 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 != types.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() != int32(connection.OPEN) { return nil, sdkerrors.Wrapf( connection.ErrInvalidConnectionState, "connection state is not OPEN (got %s)", connection.State(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") } // NOTE: the remaining code is located in the PacketExecuted function return packet, nil } // PacketExecuted writes the packet execution acknowledgement to the state, // which will be verified by the counterparty chain using AcknowledgePacket. // // CONTRACT: this function must be called in the IBC handler 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 != types.OPEN { return sdkerrors.Wrapf( types.ErrInvalidChannelState, "channel state is not OPEN (got %s)", channel.State.String(), ) } capName := host.ChannelCapabilityPath(packet.GetDestPort(), packet.GetDestChannel()) if !k.scopedKeeper.AuthenticateCapability(ctx, chanCap, capName) { return sdkerrors.Wrap( types.ErrInvalidChannelCapability, "channel capability failed authentication", ) } if len(acknowledgement) > 0 || channel.Ordering == types.UNORDERED { if _, found := k.GetPacketAcknowledgement(ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()); found { return sdkerrors.Wrapf( types.ErrInvalidPacket, "packet sequence (%d) already has been received", packet.GetSequence(), ) } k.SetPacketAcknowledgement( ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), types.CommitAcknowledgement(acknowledgement), ) } if channel.Ordering == types.ORDERED { nextSequenceRecv, found := k.GetNextSequenceRecv(ctx, packet.GetDestPort(), packet.GetDestChannel()) if !found { return sdkerrors.Wrapf( types.ErrSequenceReceiveNotFound, "destination port: %s, destination channel: %s", packet.GetDestPort(), packet.GetDestChannel(), ) } 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. Its intended usage is within the ante // handler. A subsequent call to AcknowledgementExecuted will clean 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 []byte, proofHeight uint64, ) (exported.PacketI, error) { channel, found := k.GetChannel(ctx, packet.GetSourcePort(), packet.GetSourceChannel()) if !found { return nil, sdkerrors.Wrapf( types.ErrChannelNotFound, packet.GetSourcePort(), packet.GetSourceChannel(), ) } if channel.State != types.OPEN { return nil, sdkerrors.Wrapf( types.ErrInvalidChannelState, "channel state is not OPEN (got %s)", channel.State.String(), ) } // NOTE: AcknowledgePacket 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() != int32(connection.OPEN) { return nil, sdkerrors.Wrapf( connection.ErrInvalidConnectionState, "connection state is not OPEN (got %s)", connection.State(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") } if channel.Ordering == types.ORDERED { nextSequenceAck, found := k.GetNextSequenceAck(ctx, packet.GetDestPort(), packet.GetDestChannel()) if !found { return nil, sdkerrors.Wrapf( types.ErrSequenceAckNotFound, "destination port: %s, destination channel: %s", packet.GetDestPort(), packet.GetDestChannel(), ) } if packet.GetSequence() != nextSequenceAck { return nil, sdkerrors.Wrapf( sdkerrors.ErrInvalidSequence, "packet sequence ≠ next ack sequence (%d ≠ %d)", packet.GetSequence(), nextSequenceAck, ) } k.SetNextSequenceAck(ctx, packet.GetDestPort(), packet.GetDestChannel(), nextSequenceAck+1) } // NOTE: the remaining code is located in the AcknowledgementExecuted function return packet, nil } // AcknowledgementExecuted deletes the packet commitment from this chain. // It is assumed that the acknowledgement verification has already occurred. // // CONTRACT: this function must be called in the IBC handler func (k Keeper) AcknowledgementExecuted( ctx sdk.Context, chanCap *capability.Capability, packet exported.PacketI, ) error { // sanity check _, found := k.GetChannel(ctx, packet.GetSourcePort(), packet.GetSourceChannel()) if !found { return sdkerrors.Wrapf( types.ErrChannelNotFound, packet.GetSourcePort(), packet.GetSourceChannel(), ) } capName := host.ChannelCapabilityPath(packet.GetSourcePort(), packet.GetSourceChannel()) if !k.scopedKeeper.AuthenticateCapability(ctx, chanCap, capName) { return sdkerrors.Wrap( types.ErrInvalidChannelCapability, "channel capability failed authentication", ) } k.deletePacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) // 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 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 all packets on an ordered // channel less than or equal to the packet's sequence 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. // // NOTE: this functionality is not compatible with calling AcknowledgePacket // and must be handled at the application level. func (k Keeper) CleanupPacket( ctx sdk.Context, chanCap *capability.Capability, packet exported.PacketI, proof []byte, 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 != types.OPEN { return nil, sdkerrors.Wrapf( types.ErrInvalidChannelState, "channel state is not OPEN (got %s)", channel.State.String(), ) } capName := host.ChannelCapabilityPath(packet.GetSourcePort(), packet.GetSourceChannel()) if !k.scopedKeeper.AuthenticateCapability(ctx, chanCap, capName) { return nil, sdkerrors.Wrap( types.ErrInvalidChannelCapability, "channel capability failed authentication", ) } 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 hasn't been 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 or has been cleaned up") } switch channel.Ordering { case types.ORDERED: // check that the recv sequence is as claimed if err := k.connectionKeeper.VerifyNextSequenceRecv( ctx, connectionEnd, proofHeight, proof, packet.GetDestPort(), packet.GetDestChannel(), nextSequenceRecv, ); err != nil { return nil, sdkerrors.Wrap( client.ErrFailedNextSeqRecvVerification, err.Error(), ) } // delete all packet commitments with a sequence less than or equal to the packet's sequence k.deletePacketCommitmentsLTE(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) case types.UNORDERED: if err := k.connectionKeeper.VerifyPacketAcknowledgement( ctx, connectionEnd, proofHeight, proof, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), acknowledgement, ); err != nil { return nil, sdkerrors.Wrap( client.ErrFailedPacketAckVerification, err.Error(), ) } // delete the packet commitment k.deletePacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) default: panic(sdkerrors.Wrapf(types.ErrInvalidChannelOrdering, channel.Ordering.String())) } // 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 }