-
Notifications
You must be signed in to change notification settings - Fork 586
/
relay.go
155 lines (127 loc) · 5.89 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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package keeper
import (
"context"
"github.com/cosmos/gogoproto/proto"
errorsmod "cosmossdk.io/errors"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/ibc-go/v9/modules/apps/27-interchain-accounts/host/types"
icatypes "github.com/cosmos/ibc-go/v9/modules/apps/27-interchain-accounts/types"
channeltypes "github.com/cosmos/ibc-go/v9/modules/core/04-channel/types"
ibcerrors "github.com/cosmos/ibc-go/v9/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 context.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 context.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
sdkCtx := sdk.UnwrapSDKContext(ctx) // TODO: https://github.com/cosmos/ibc-go/issues/5917
cacheCtx, writeCache := sdkCtx.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 context.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) { // TODO: https://github.com/cosmos/ibc-go/issues/7223
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
}