cosmos-sdk/x/ibc/04-channel/keeper/packet.go

464 lines
17 KiB
Go

package keeper
import (
"bytes"
"fmt"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types"
clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types"
connectiontypes "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/types"
"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 *capabilitytypes.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(connectiontypes.ErrConnectionNotFound, channel.ConnectionHops[0])
}
// NOTE: assume UNINITIALIZED is a closed connection
if connectionEnd.GetState() == int32(connectiontypes.UNINITIALIZED) {
return sdkerrors.Wrap(
connectiontypes.ErrInvalidConnectionState,
"connection is UNINITIALIZED",
)
}
clientState, found := k.clientKeeper.GetClientState(ctx, connectionEnd.GetClientID())
if !found {
return clienttypes.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,
) error {
channel, found := k.GetChannel(ctx, packet.GetDestPort(), packet.GetDestChannel())
if !found {
return sdkerrors.Wrap(types.ErrChannelNotFound, packet.GetDestChannel())
}
if channel.State != types.OPEN {
return 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 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 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 sdkerrors.Wrap(connectiontypes.ErrConnectionNotFound, channel.ConnectionHops[0])
}
if connectionEnd.GetState() != int32(connectiontypes.OPEN) {
return sdkerrors.Wrapf(
connectiontypes.ErrInvalidConnectionState,
"connection state is not OPEN (got %s)", connectiontypes.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 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 sdkerrors.Wrapf(
types.ErrPacketTimeout,
"block timestamp >= packet timeout timestamp (%s >= %s)", ctx.BlockTime(), time.Unix(0, int64(packet.GetTimeoutTimestamp())),
)
}
// check if the packet acknowledgement has been received already for unordered channels
if channel.Ordering == types.UNORDERED {
_, found := k.GetPacketAcknowledgement(ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence())
if found {
return sdkerrors.Wrapf(
types.ErrInvalidPacket,
"packet sequence (%d) already has been received", packet.GetSequence(),
)
}
}
// check if the packet is being received in order
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,
)
}
}
if err := k.connectionKeeper.VerifyPacketCommitment(
ctx, connectionEnd, proofHeight, proof,
packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence(),
types.CommitPacket(packet),
); err != nil {
return sdkerrors.Wrap(err, "couldn't verify counterparty packet commitment")
}
// NOTE: the remaining code is located in the PacketExecuted function
return 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 *capabilitytypes.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 {
return sdkerrors.Wrap(types.ErrInvalidAcknowledgement, "acknowledgement cannot be empty")
}
// always set the acknowledgement so that it can be verified on the other side
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(),
)
}
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,
) error {
channel, found := k.GetChannel(ctx, packet.GetSourcePort(), packet.GetSourceChannel())
if !found {
return sdkerrors.Wrapf(
types.ErrChannelNotFound,
packet.GetSourcePort(), packet.GetSourceChannel(),
)
}
if channel.State != types.OPEN {
return 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 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(connectiontypes.ErrConnectionNotFound, channel.ConnectionHops[0])
}
if connectionEnd.GetState() != int32(connectiontypes.OPEN) {
return sdkerrors.Wrapf(
connectiontypes.ErrInvalidConnectionState,
"connection state is not OPEN (got %s)", connectiontypes.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 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 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 sdkerrors.Wrapf(
types.ErrSequenceAckNotFound,
"destination port: %s, destination channel: %s", packet.GetDestPort(), packet.GetDestChannel(),
)
}
if packet.GetSequence() != nextSequenceAck {
return 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 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 *capabilitytypes.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
}