-
Notifications
You must be signed in to change notification settings - Fork 586
/
relay.go
133 lines (109 loc) · 4.95 KB
/
relay.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package keeper
import (
errorsmod "cosmossdk.io/errors"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/gogoproto/proto"
ibcerrors "github.com/cosmos/ibc-go/v7/internal/errors"
"github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/host/types"
icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types"
channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types"
)
// 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
if err := icatypes.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil {
// UnmarshalJSON errors are indeterminate and therefore are not wrapped and included in failed acks
return nil, errorsmod.Wrapf(icatypes.ErrUnknownDataType, "cannot unmarshal ICS-27 interchain account packet data")
}
switch data.Type {
case icatypes.EXECUTE_TX:
msgs, err := icatypes.DeserializeCosmosTx(k.cdc, data.Data)
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 err := msg.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.GetAllowMessages(ctx)
for _, msg := range msgs {
if !types.ContainsMsgType(allowMsgs, msg) {
return errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "message type not allowed: %s", sdk.MsgTypeURL(msg))
}
for _, signer := range msg.GetSigners() {
if interchainAccountAddr != signer.String() {
return errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "unexpected signer address: expected %s, got %s", interchainAccountAddr, 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
}