mukan-ibc/modules/core/04-channel/keeper/packet.go
Mukan Erkin Törük 6852832fe8
Some checks failed
CodeQL / Analyze (push) Waiting to run
Docker Build & Push Simapp (main) / docker-build (push) Waiting to run
golangci-lint / lint (push) Waiting to run
Tests / Code Coverage / build (amd64) (push) Waiting to run
Tests / Code Coverage / build (arm64) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[additional-args:-tags="test_e2e" name:e2e path:./e2e]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:08-wasm path:./modules/light-clients/08-wasm]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:ibc-go path:.]) (push) Waiting to run
Deploy to GitHub Pages / Deploy to GitHub Pages (push) Has been cancelled
Buf-Push / push (push) Has been cancelled
initial: sovereign Mukan Network fork
2026-05-11 03:18:28 +03:00

449 lines
18 KiB
Go

package keeper
import (
"bytes"
"strconv"
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types"
connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types"
"github.com/cosmos/ibc-go/v10/modules/core/04-channel/types"
"github.com/cosmos/ibc-go/v10/modules/core/exported"
)
// SendPacket is called by a module in order to send an IBC packet on a channel.
// The packet sequence generated for the packet to be sent is returned. An error
// is returned if one occurs.
func (k *Keeper) SendPacket(
ctx sdk.Context,
sourcePort string,
sourceChannel string,
timeoutHeight clienttypes.Height,
timeoutTimestamp uint64,
data []byte,
) (uint64, error) {
channel, found := k.GetChannel(ctx, sourcePort, sourceChannel)
if !found {
return 0, errorsmod.Wrap(types.ErrChannelNotFound, sourceChannel)
}
if channel.State != types.OPEN {
return 0, errorsmod.Wrapf(types.ErrInvalidChannelState, "channel is not OPEN (got %s)", channel.State)
}
sequence, found := k.GetNextSequenceSend(ctx, sourcePort, sourceChannel)
if !found {
return 0, errorsmod.Wrapf(
types.ErrSequenceSendNotFound,
"source port: %s, source channel: %s", sourcePort, sourceChannel,
)
}
// construct packet from given fields and channel state
packet := types.NewPacket(data, sequence, sourcePort, sourceChannel,
channel.Counterparty.PortId, channel.Counterparty.ChannelId, timeoutHeight, timeoutTimestamp)
if err := packet.ValidateBasic(); err != nil {
return 0, errorsmod.Wrap(err, "constructed packet failed basic validation")
}
connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0])
if !found {
return 0, errorsmod.Wrap(connectiontypes.ErrConnectionNotFound, channel.ConnectionHops[0])
}
// prevent accidental sends with clients that cannot be updated
if status := k.clientKeeper.GetClientStatus(ctx, connectionEnd.ClientId); status != exported.Active {
return 0, errorsmod.Wrapf(clienttypes.ErrClientNotActive, "cannot send packet using client (%s) with status %s", connectionEnd.ClientId, status)
}
latestHeight := k.clientKeeper.GetClientLatestHeight(ctx, connectionEnd.ClientId)
if latestHeight.IsZero() {
return 0, errorsmod.Wrapf(clienttypes.ErrInvalidHeight, "cannot send packet using client (%s) with zero height", connectionEnd.ClientId)
}
latestTimestamp, err := k.clientKeeper.GetClientTimestampAtHeight(ctx, connectionEnd.ClientId, latestHeight)
if err != nil {
return 0, err
}
// check if packet is timed out on the receiving chain
timeout := types.NewTimeout(packet.GetTimeoutHeight().(clienttypes.Height), packet.GetTimeoutTimestamp())
if timeout.Elapsed(latestHeight, latestTimestamp) {
return 0, errorsmod.Wrap(timeout.ErrTimeoutElapsed(latestHeight, latestTimestamp), "invalid packet timeout")
}
commitment := types.CommitPacket(packet)
k.SetNextSequenceSend(ctx, sourcePort, sourceChannel, sequence+1)
k.SetPacketCommitment(ctx, sourcePort, sourceChannel, packet.GetSequence(), commitment)
emitSendPacketEvent(ctx, packet, channel, timeoutHeight)
k.Logger(ctx).Info(
"packet sent",
"sequence", strconv.FormatUint(packet.GetSequence(), 10),
"src_port", sourcePort,
"src_channel", sourceChannel,
"dst_port", packet.GetDestPort(),
"dst_channel", packet.GetDestChannel(),
)
return packet.GetSequence(), 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 types.Packet,
proof []byte,
proofHeight exported.Height,
) (string, error) {
channel, found := k.GetChannel(ctx, packet.GetDestPort(), packet.GetDestChannel())
if !found {
return "", errorsmod.Wrap(types.ErrChannelNotFound, packet.GetDestChannel())
}
if channel.State != types.OPEN {
return "", errorsmod.Wrapf(types.ErrInvalidChannelState, "channel is not OPEN (got %s)", channel.State)
}
// packet must come from the channel's counterparty
if packet.GetSourcePort() != channel.Counterparty.PortId {
return "", errorsmod.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 "", errorsmod.Wrapf(
types.ErrInvalidPacket,
"packet source channel doesn't match the counterparty's channel (%s ≠ %s)", packet.GetSourceChannel(), channel.Counterparty.ChannelId,
)
}
// Connection must be OPEN to receive a packet. It is possible for connection to not yet be open if packet was
// sent optimistically before connection and channel handshake completed. However, to receive a packet,
// connection and channel must both be open
connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0])
if !found {
return "", errorsmod.Wrap(connectiontypes.ErrConnectionNotFound, channel.ConnectionHops[0])
}
if connectionEnd.State != connectiontypes.OPEN {
return "", errorsmod.Wrapf(connectiontypes.ErrInvalidConnectionState, "connection state is not OPEN (got %s)", connectionEnd.State)
}
// check if packet timed out by comparing it with the latest height of the chain
selfHeight, selfTimestamp := clienttypes.GetSelfHeight(ctx), uint64(ctx.BlockTime().UnixNano())
timeout := types.NewTimeout(packet.GetTimeoutHeight().(clienttypes.Height), packet.GetTimeoutTimestamp())
if timeout.Elapsed(selfHeight, selfTimestamp) {
return "", errorsmod.Wrap(timeout.ErrTimeoutElapsed(selfHeight, selfTimestamp), "packet timeout elapsed")
}
commitment := types.CommitPacket(packet)
// verify that the counterparty did commit to sending this packet
if err := k.connectionKeeper.VerifyPacketCommitment(
ctx, connectionEnd, proofHeight, proof,
packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence(),
commitment,
); err != nil {
return "", errorsmod.Wrap(err, "couldn't verify counterparty packet commitment")
}
if err := k.applyReplayProtection(ctx, packet, channel); err != nil {
return "", err
}
// log that a packet has been received & executed
k.Logger(ctx).Info(
"packet received",
"sequence", strconv.FormatUint(packet.GetSequence(), 10),
"src_port", packet.GetSourcePort(),
"src_channel", packet.GetSourceChannel(),
"dst_port", packet.GetDestPort(),
"dst_channel", packet.GetDestChannel(),
)
// emit an event that the relayer can query for
emitRecvPacketEvent(ctx, packet, channel)
return channel.Version, nil
}
// applyReplayProtection ensures a packet has not already been received
// and performs the necessary state changes to ensure it cannot be received again.
func (k *Keeper) applyReplayProtection(ctx sdk.Context, packet types.Packet, channel types.Channel) error {
// REPLAY PROTECTION: The recvStartSequence will prevent historical proofs from allowing replay
// attacks on packets processed in previous lifecycles of a channel. After a successful channel
// upgrade all packets under the recvStartSequence will have been processed and thus should be
// rejected.
recvStartSequence, _ := k.GetRecvStartSequence(ctx, packet.GetDestPort(), packet.GetDestChannel())
if packet.GetSequence() < recvStartSequence {
return errorsmod.Wrap(types.ErrPacketReceived, "packet already processed in previous channel upgrade")
}
switch channel.Ordering {
case types.UNORDERED:
// REPLAY PROTECTION: Packet receipts will indicate that a packet has already been received
// on unordered channels. Packet receipts must not be pruned, unless it has been marked stale
// by the increase of the recvStartSequence.
_, found := k.GetPacketReceipt(ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence())
if found {
emitRecvPacketEvent(ctx, packet, channel)
// This error indicates that the packet has already been relayed. Core IBC will
// treat this error as a no-op in order to prevent an entire relay transaction
// from failing and consuming unnecessary fees.
return types.ErrNoOpMsg
}
// All verification complete, update state
// For unordered channels we must set the receipt so it can be verified on the other side.
// This receipt does not contain any data, since the packet has not yet been processed,
// it's just a single store key set to a single byte to indicate that the packet has been received
k.SetPacketReceipt(ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence())
case types.ORDERED:
// check if the packet is being received in order
nextSequenceRecv, found := k.GetNextSequenceRecv(ctx, packet.GetDestPort(), packet.GetDestChannel())
if !found {
return errorsmod.Wrapf(
types.ErrSequenceReceiveNotFound,
"destination port: %s, destination channel: %s", packet.GetDestPort(), packet.GetDestChannel(),
)
}
if packet.GetSequence() < nextSequenceRecv {
emitRecvPacketEvent(ctx, packet, channel)
// This error indicates that the packet has already been relayed. Core IBC will
// treat this error as a no-op in order to prevent an entire relay transaction
// from failing and consuming unnecessary fees.
return types.ErrNoOpMsg
}
// REPLAY PROTECTION: Ordered channels require packets to be received in a strict order.
// Any out of order or previously received packets are rejected.
if packet.GetSequence() != nextSequenceRecv {
return errorsmod.Wrapf(
types.ErrPacketSequenceOutOfOrder,
"packet sequence ≠ next receive sequence (%d ≠ %d)", packet.GetSequence(), nextSequenceRecv,
)
}
// All verification complete, update state
// In ordered case, we must increment nextSequenceRecv
nextSequenceRecv++
// incrementing nextSequenceRecv and storing under this chain's channelEnd identifiers
// Since this is the receiving chain, our channelEnd is packet's destination port and channel
k.SetNextSequenceRecv(ctx, packet.GetDestPort(), packet.GetDestChannel(), nextSequenceRecv)
}
return nil
}
// WriteAcknowledgement writes the packet execution acknowledgement to the state,
// which will be verified by the counterparty chain using AcknowledgePacket.
//
// CONTRACT:
//
// 1) For synchronous execution, this function is be called in the IBC handler .
// For async handling, it needs to be called directly by the module which originally
// processed the packet.
//
// 2) Assumes that packet receipt has been written (unordered), or nextSeqRecv was incremented (ordered)
// previously by RecvPacket.
func (k *Keeper) WriteAcknowledgement(
ctx sdk.Context,
packet exported.PacketI,
acknowledgement exported.Acknowledgement,
) error {
channel, found := k.GetChannel(ctx, packet.GetDestPort(), packet.GetDestChannel())
if !found {
return errorsmod.Wrap(types.ErrChannelNotFound, packet.GetDestChannel())
}
if channel.State != types.OPEN {
return errorsmod.Wrapf(types.ErrInvalidChannelState, "channel is not OPEN (got %s)", channel.State)
}
// REPLAY PROTECTION: The recvStartSequence will prevent historical proofs from allowing replay
// attacks on packets processed in previous lifecycles of a channel. After a successful channel
// upgrade all packets under the recvStartSequence will have been processed and thus should be
// rejected. Any asynchronous acknowledgement writes from packets processed in a previous lifecycle of a channel
// will also be rejected.
recvStartSequence, _ := k.GetRecvStartSequence(ctx, packet.GetDestPort(), packet.GetDestChannel())
if packet.GetSequence() < recvStartSequence {
return errorsmod.Wrap(types.ErrPacketReceived, "packet already processed in previous channel upgrade")
}
// NOTE: IBC app modules might have written the acknowledgement synchronously on
// the OnRecvPacket callback so we need to check if the acknowledgement is already
// set on the store and return an error if so.
if k.HasPacketAcknowledgement(ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) {
return types.ErrAcknowledgementExists
}
if acknowledgement == nil {
return errorsmod.Wrap(types.ErrInvalidAcknowledgement, "acknowledgement cannot be nil")
}
bz := acknowledgement.Acknowledgement()
if len(bz) == 0 {
return errorsmod.Wrap(types.ErrInvalidAcknowledgement, "acknowledgement cannot be empty")
}
// set the acknowledgement so that it can be verified on the other side
k.SetPacketAcknowledgement(
ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(),
types.CommitAcknowledgement(bz),
)
// log that a packet acknowledgement has been written
k.Logger(ctx).Info(
"acknowledgement written",
"sequence", strconv.FormatUint(packet.GetSequence(), 10),
"src_port", packet.GetSourcePort(),
"src_channel", packet.GetSourceChannel(),
"dst_port", packet.GetDestPort(),
"dst_channel", packet.GetDestChannel(),
)
emitWriteAcknowledgementEvent(ctx, packet.(types.Packet), channel, bz)
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. AcknowledgePacket will clean up the packet commitment,
// which is no longer necessary since the packet has been received and acted upon.
// It will also increment NextSequenceAck in case of ORDERED channels.
func (k *Keeper) AcknowledgePacket(
ctx sdk.Context,
packet types.Packet,
acknowledgement []byte,
proof []byte,
proofHeight exported.Height,
) (string, error) {
channel, found := k.GetChannel(ctx, packet.GetSourcePort(), packet.GetSourceChannel())
if !found {
return "", errorsmod.Wrapf(
types.ErrChannelNotFound,
"port ID (%s) channel ID (%s)", packet.GetSourcePort(), packet.GetSourceChannel(),
)
}
if channel.State != types.OPEN {
return "", errorsmod.Wrapf(types.ErrInvalidChannelState, "channel is not OPEN (got %s)", channel.State)
}
// packet must have been sent to the channel's counterparty
if packet.GetDestPort() != channel.Counterparty.PortId {
return "", errorsmod.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 "", errorsmod.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 "", errorsmod.Wrap(connectiontypes.ErrConnectionNotFound, channel.ConnectionHops[0])
}
if connectionEnd.State != connectiontypes.OPEN {
return "", errorsmod.Wrapf(connectiontypes.ErrInvalidConnectionState, "connection state is not OPEN (got %s)", connectionEnd.State)
}
commitment := k.GetPacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence())
if len(commitment) == 0 {
emitAcknowledgePacketEvent(ctx, packet, channel)
// This error indicates that the acknowledgement has already been relayed
// or there is a misconfigured relayer attempting to prove an acknowledgement
// for a packet never sent. Core IBC will treat this error as a no-op in order to
// prevent an entire relay transaction from failing and consuming unnecessary fees.
return "", types.ErrNoOpMsg
}
packetCommitment := types.CommitPacket(packet)
var ack types.Acknowledgement
err := types.SubModuleCdc.UnmarshalJSON(acknowledgement, &ack)
if err == nil {
ackBz := ack.Acknowledgement()
if !bytes.Equal(ackBz, acknowledgement) {
return "", errorsmod.Wrap(types.ErrInvalidAcknowledgement, "acknowledgement marshalling error")
}
}
// verify we sent the packet and haven't cleared it out yet
if !bytes.Equal(commitment, packetCommitment) {
return "", errorsmod.Wrapf(types.ErrInvalidPacket, "commitment bytes are not equal: got (%v), expected (%v)", packetCommitment, commitment)
}
if err := k.connectionKeeper.VerifyPacketAcknowledgement(
ctx, connectionEnd, proofHeight, proof, packet.GetDestPort(), packet.GetDestChannel(),
packet.GetSequence(), acknowledgement,
); err != nil {
return "", err
}
// assert packets acknowledged in order
if channel.Ordering == types.ORDERED {
nextSequenceAck, found := k.GetNextSequenceAck(ctx, packet.GetSourcePort(), packet.GetSourceChannel())
if !found {
return "", errorsmod.Wrapf(
types.ErrSequenceAckNotFound,
"source port: %s, source channel: %s", packet.GetSourcePort(), packet.GetSourceChannel(),
)
}
if packet.GetSequence() != nextSequenceAck {
return "", errorsmod.Wrapf(
types.ErrPacketSequenceOutOfOrder,
"packet sequence ≠ next ack sequence (%d ≠ %d)", packet.GetSequence(), nextSequenceAck,
)
}
// All verification complete, in the case of ORDERED channels we must increment nextSequenceAck
nextSequenceAck++
// incrementing NextSequenceAck and storing under this chain's channelEnd identifiers
// Since this is the original sending chain, our channelEnd is packet's source port and channel
k.SetNextSequenceAck(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), nextSequenceAck)
}
// Delete packet commitment, since the packet has been acknowledged, the commitement is no longer necessary
k.deletePacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence())
// log that a packet has been acknowledged
k.Logger(ctx).Info(
"packet acknowledged",
"sequence", strconv.FormatUint(packet.GetSequence(), 10),
"src_port", packet.GetSourcePort(),
"src_channel", packet.GetSourceChannel(),
"dst_port", packet.GetDestPort(),
"dst_channel", packet.GetDestChannel(),
)
// emit an event marking that we have processed the acknowledgement
emitAcknowledgePacketEvent(ctx, packet, channel)
return channel.Version, nil
}