mukan-ibc/modules/apps/27-interchain-accounts/host/keeper/relay.go
Mukan Erkin Törük 88dd97a9f8
Some checks failed
CodeQL / Analyze (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
Docker Build & Push Simapp (main) / docker-build (push) Has been cancelled
refactor: replace all github.com upstream refs with git.cw.tr/mukan-network
2026-05-11 03:36:22 +03:00

152 lines
5.8 KiB
Go

package keeper
import (
"github.com/cosmos/gogoproto/proto"
errorsmod "cosmossdk.io/errors"
codectypes "git.cw.tr/mukan-network/mukan-sdk/codec/types"
sdk "git.cw.tr/mukan-network/mukan-sdk/types"
"git.cw.tr/mukan-network/mukan-ibc/modules/apps/27-interchain-accounts/host/types"
icatypes "git.cw.tr/mukan-network/mukan-ibc/modules/apps/27-interchain-accounts/types"
channeltypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/04-channel/types"
ibcerrors "git.cw.tr/mukan-network/mukan-ibc/modules/core/errors"
)
// OnRecvPacket handles a given interchain accounts packet on a destination host chain.
// If the transaction is successfully executed, the transaction response bytes will be returned.
func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet) ([]byte, error) {
var data icatypes.InterchainAccountPacketData
err := data.UnmarshalJSON(packet.GetData())
if err != nil {
// UnmarshalJSON errors are indeterminate and therefore are not wrapped and included in failed acks
return nil, errorsmod.Wrapf(ibcerrors.ErrInvalidType, "cannot unmarshal ICS-27 interchain account packet data")
}
metadata, err := k.getAppMetadata(ctx, packet.DestinationPort, packet.DestinationChannel)
if err != nil {
return nil, err
}
switch data.Type {
case icatypes.EXECUTE_TX:
msgs, err := icatypes.DeserializeCosmosTx(k.cdc, data.Data, metadata.Encoding)
if err != nil {
return nil, errorsmod.Wrapf(err, "failed to deserialize interchain account transaction")
}
txResponse, err := k.executeTx(ctx, packet.SourcePort, packet.DestinationPort, packet.DestinationChannel, msgs)
if err != nil {
return nil, errorsmod.Wrapf(err, "failed to execute interchain account transaction")
}
return txResponse, nil
default:
return nil, icatypes.ErrUnknownDataType
}
}
// executeTx attempts to execute the provided transaction. It begins by authenticating the transaction signer.
// If authentication succeeds, it does basic validation of the messages before attempting to deliver each message
// into state. The state changes will only be committed if all messages in the transaction succeed. Thus the
// execution of the transaction is atomic, all state changes are reverted if a single message fails.
func (k Keeper) executeTx(ctx sdk.Context, sourcePort, destPort, destChannel string, msgs []sdk.Msg) ([]byte, error) {
channel, found := k.channelKeeper.GetChannel(ctx, destPort, destChannel)
if !found {
return nil, channeltypes.ErrChannelNotFound
}
if err := k.authenticateTx(ctx, msgs, channel.ConnectionHops[0], sourcePort); err != nil {
return nil, err
}
txMsgData := &sdk.TxMsgData{
MsgResponses: make([]*codectypes.Any, len(msgs)),
}
// CacheContext returns a new context with the multi-store branched into a cached storage object
// writeCache is called only if all msgs succeed, performing state transitions atomically
cacheCtx, writeCache := ctx.CacheContext()
for i, msg := range msgs {
if m, ok := msg.(sdk.HasValidateBasic); ok {
if err := m.ValidateBasic(); err != nil {
return nil, err
}
}
protoAny, err := k.executeMsg(cacheCtx, msg)
if err != nil {
return nil, err
}
txMsgData.MsgResponses[i] = protoAny
}
writeCache()
txResponse, err := proto.Marshal(txMsgData)
if err != nil {
return nil, errorsmod.Wrap(err, "failed to marshal tx data")
}
return txResponse, nil
}
// authenticateTx ensures the provided msgs contain the correct interchain account signer address retrieved
// from state using the provided controller port identifier
func (k Keeper) authenticateTx(ctx sdk.Context, msgs []sdk.Msg, connectionID, portID string) error {
interchainAccountAddr, found := k.GetInterchainAccountAddress(ctx, connectionID, portID)
if !found {
return errorsmod.Wrapf(icatypes.ErrInterchainAccountNotFound, "failed to retrieve interchain account on port %s", portID)
}
allowMsgs := k.GetParams(ctx).AllowMessages
for _, msg := range msgs {
if !types.ContainsMsgType(allowMsgs, msg) {
return errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "message type not allowed: %s", sdk.MsgTypeURL(msg))
}
// obtain the message signers using the proto signer annotations
// the msgv2 return value is discarded as it is not used
signers, _, err := k.cdc.GetMsgV1Signers(msg)
if err != nil {
return errorsmod.Wrapf(err, "failed to obtain message signers for message type %s", sdk.MsgTypeURL(msg))
}
for _, signer := range signers {
// the interchain account address is stored as the string value of the sdk.AccAddress type
// thus we must cast the signer to a sdk.AccAddress to obtain the comparison value
// the stored interchain account address must match the signer for every message to be executed
if interchainAccountAddr != sdk.AccAddress(signer).String() {
return errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "unexpected signer address: expected %s, got %s", interchainAccountAddr, sdk.AccAddress(signer).String())
}
}
}
return nil
}
// Attempts to get the message handler from the router and if found will then execute the message.
// If the message execution is successful, the proto marshaled message response will be returned.
func (k Keeper) executeMsg(ctx sdk.Context, msg sdk.Msg) (*codectypes.Any, error) {
handler := k.msgRouter.Handler(msg)
if handler == nil {
return nil, icatypes.ErrInvalidRoute
}
res, err := handler(ctx, msg)
if err != nil {
return nil, err
}
// NOTE: The sdk msg handler creates a new EventManager, so events must be correctly propagated back to the current context
ctx.EventManager().EmitEvents(res.GetEvents())
// Each individual sdk.Result has exactly one Msg response. We aggregate here.
msgResponse := res.MsgResponses[0]
if msgResponse == nil {
return nil, errorsmod.Wrapf(ibcerrors.ErrLogic, "got nil Msg response for msg %s", sdk.MsgTypeURL(msg))
}
return msgResponse, nil
}