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
449 lines
18 KiB
Go
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
|
|
}
|