Skip to content

Commit b53d06c

Browse files
colin-axnermergify-bot
authored and
mergify-bot
committed
refactor: include transaction response in ics27 channel acknowledgement (#811)
## Description ref: #701 --- Before we can merge this PR, please make sure that all the following items have been checked off. If any of the checklist items are not applicable, please leave them but write a little note why. - [x] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#pr-targeting)) - [x] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [x] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - [x] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/master/CONTRIBUTING.md#testing) - [x] Updated relevant documentation (`docs/`) or specification (`x/<module>/spec/`) - [x] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [x] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [x] Re-reviewed `Files changed` in the Github PR explorer - [x] Review `Codecov Report` in the comment section below once CI passes (cherry picked from commit 6c48f7e)
1 parent 578847c commit b53d06c

File tree

5 files changed

+256
-29
lines changed

5 files changed

+256
-29
lines changed

docs/app-modules/interchain-accounts/auth-modules.md

+117
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,123 @@ seq, err = keeper.icaControllerKeeper.SendTx(ctx, chanCap, portID, packetData, t
210210
The data within an `InterchainAccountPacketData` must be serialized using a format supported by the host chain.
211211
If the host chain is using the ibc-go host chain submodule, `SerializeCosmosTx` should be used. If the `InterchainAccountPacketData.Data` is serialized using a format not support by the host chain, the packet will not be successfully received.
212212

213+
## `OnAcknowledgementPacket`
214+
215+
Controller chains will be able to access the acknowledgement written into the host chain state once a relayer relays the acknowledgement.
216+
The acknowledgement bytes will be passed to the auth module via the `OnAcknowledgementPacket` callback.
217+
Auth modules are expected to know how to decode the acknowledgement.
218+
219+
If the controller chain is connected to a host chain using the host module on ibc-go, it may interpret the acknowledgement bytes as follows:
220+
221+
Begin by unmarshaling the acknowledgement into sdk.TxMsgData:
222+
```go
223+
txMsgData := &sdk.TxMsgData{}
224+
if err := proto.Unmarshal(ack.Acknowledgement(), txMsgData); err != nil {
225+
return err
226+
}
227+
```
228+
229+
If the txMsgData.Data field is non nil, the host chain is using SDK version <= v0.45.
230+
The auth module should interpret the txMsgData.Data as follows:
231+
232+
```go
233+
switch len(txMsgData.Data) {
234+
case 0:
235+
for _, msgData := range txMsgData.Data {
236+
if err := handler(msgData); err != nil {
237+
return err
238+
}
239+
}
240+
...
241+
}
242+
```
243+
244+
A handler will be needed to interpret what actions to perform based on the message type sent.
245+
A router could be used, or more simply a switch statement.
246+
247+
```go
248+
func handler(msgData sdk.MsgData) error {
249+
switch msgData.TypeURL {
250+
case banktypes.MsgSend:
251+
msgResponse := &banktypes.MsgSendResponse{}
252+
if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil {
253+
return err
254+
}
255+
256+
handleBankSendMsg(msgResponse)
257+
258+
case stakingtypes.MsgDelegate:
259+
msgResponse := &stakingtypes.MsgDelegateResponse{}
260+
if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil {
261+
return err
262+
}
263+
264+
handleStakingDelegateMsg(msgResponse)
265+
266+
case transfertypes.MsgTransfer:
267+
msgResponse := &transfertypes.MsgTransferResponse{}
268+
if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil {
269+
return err
270+
}
271+
272+
handleIBCTransferMsg(msgResponse)
273+
274+
default:
275+
return
276+
}
277+
```
278+
279+
If the txMsgData.Data is empty, the host chain is using SDK version > v0.45.
280+
The auth module should interpret the txMsgData.Responses as follows:
281+
282+
```go
283+
...
284+
// switch statement from above continued
285+
default:
286+
for _, any := range txMsgData.MsgResponses {
287+
if err := handleAny(any); err != nil {
288+
return err
289+
}
290+
}
291+
}
292+
```
293+
294+
A handler will be needed to interpret what actions to perform based on the type url of the Any.
295+
A router could be used, or more simply a switch statement.
296+
It may be possible to deduplicate logic between `handler` and `handleAny`.
297+
298+
```go
299+
func handleAny(any *codectypes.Any) error {
300+
switch any.TypeURL {
301+
case banktypes.MsgSend:
302+
msgResponse, err := unpackBankMsgSendResponse(any)
303+
if err != nil {
304+
return err
305+
}
306+
307+
handleBankSendMsg(msgResponse)
308+
309+
case stakingtypes.MsgDelegate:
310+
msgResponse, err := unpackStakingDelegateResponse(any)
311+
if err != nil {
312+
return err
313+
}
314+
315+
handleStakingDelegateMsg(msgResponse)
316+
317+
case transfertypes.MsgTransfer:
318+
msgResponse, err := unpackIBCTransferMsgResponse(any)
319+
if err != nil {
320+
return err
321+
}
322+
323+
handleIBCTransferMsg(msgResponse)
324+
325+
default:
326+
return
327+
}
328+
```
329+
213330
### Integration into `app.go` file
214331
215332
To integrate the authentication module into your chain, please follow the steps outlined above in [app.go integration](./integration.md#example-integration).

modules/apps/27-interchain-accounts/host/ibc_module.go

+5-6
Original file line numberDiff line numberDiff line change
@@ -108,17 +108,16 @@ func (im IBCModule) OnRecvPacket(
108108
return channeltypes.NewErrorAcknowledgement(types.ErrHostSubModuleDisabled.Error())
109109
}
110110

111-
ack := channeltypes.NewResultAcknowledgement([]byte{byte(1)})
112-
113-
if err := im.keeper.OnRecvPacket(ctx, packet); err != nil {
114-
ack = types.NewErrorAcknowledgement(err)
115-
111+
txResponse, err := im.keeper.OnRecvPacket(ctx, packet)
112+
if err != nil {
116113
// Emit an event including the error msg
117114
keeper.EmitWriteErrorAcknowledgementEvent(ctx, packet, err)
115+
116+
return types.NewErrorAcknowledgement(err)
118117
}
119118

120119
// NOTE: acknowledgement will be written synchronously during IBC handler execution.
121-
return ack
120+
return channeltypes.NewResultAcknowledgement(txResponse)
122121
}
123122

124123
// OnAcknowledgementPacket implements the IBCModule interface

modules/apps/27-interchain-accounts/host/ibc_module_test.go

+86-1
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ import (
77
sdk "github.com/cosmos/cosmos-sdk/types"
88
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
99
capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types"
10+
"github.com/gogo/protobuf/proto"
1011
"github.com/stretchr/testify/suite"
12+
abcitypes "github.com/tendermint/tendermint/abci/types"
1113
"github.com/tendermint/tendermint/crypto"
14+
tmprotostate "github.com/tendermint/tendermint/proto/tendermint/state"
15+
tmstate "github.com/tendermint/tendermint/state"
1216

1317
"github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/host/types"
1418
icatypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/types"
@@ -458,6 +462,22 @@ func (suite *InterchainAccountsTestSuite) TestOnRecvPacket() {
458462
}
459463
packetData = icaPacketData.GetBytes()
460464

465+
// build expected ack
466+
msgResponseBz, err := proto.Marshal(&banktypes.MsgSendResponse{})
467+
suite.Require().NoError(err)
468+
469+
msgData := &sdk.MsgData{
470+
MsgType: sdk.MsgTypeURL(msg),
471+
Data: msgResponseBz,
472+
}
473+
474+
expectedTxResponse, err := proto.Marshal(&sdk.TxMsgData{
475+
Data: []*sdk.MsgData{msgData},
476+
})
477+
suite.Require().NoError(err)
478+
479+
expectedAck := channeltypes.NewResultAcknowledgement(expectedTxResponse)
480+
461481
params := types.NewParams(true, []string{sdk.MsgTypeURL(msg)})
462482
suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params)
463483

@@ -476,7 +496,12 @@ func (suite *InterchainAccountsTestSuite) TestOnRecvPacket() {
476496
suite.Require().True(ok)
477497

478498
ack := cbs.OnRecvPacket(suite.chainB.GetContext(), packet, nil)
479-
suite.Require().Equal(tc.expAckSuccess, ack.Success())
499+
if tc.expAckSuccess {
500+
suite.Require().True(ack.Success())
501+
suite.Require().Equal(expectedAck, ack)
502+
} else {
503+
suite.Require().False(ack.Success())
504+
}
480505

481506
})
482507
}
@@ -685,3 +710,63 @@ func (suite *InterchainAccountsTestSuite) TestControlAccountAfterChannelClose()
685710
hasBalance = suite.chainB.GetSimApp().BankKeeper.HasBalance(suite.chainB.GetContext(), icaAddr, sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: sdk.NewInt(0)})
686711
suite.Require().True(hasBalance)
687712
}
713+
714+
// The safety of including SDK MsgResponses in the acknowledgement rests
715+
// on the inclusion of the abcitypes.ResponseDeliverTx.Data in the
716+
// abcitypes.ResposneDeliverTx hash. If the abcitypes.ResponseDeliverTx.Data
717+
// gets removed from consensus they must no longer be used in the packet
718+
// acknowledgement.
719+
//
720+
// This test acts as an indicator that the abcitypes.ResponseDeliverTx.Data
721+
// may no longer be deterministic.
722+
func (suite *InterchainAccountsTestSuite) TestABCICodeDeterminism() {
723+
msgResponseBz, err := proto.Marshal(&channeltypes.MsgChannelOpenInitResponse{})
724+
suite.Require().NoError(err)
725+
726+
msgData := &sdk.MsgData{
727+
MsgType: sdk.MsgTypeURL(&channeltypes.MsgChannelOpenInit{}),
728+
Data: msgResponseBz,
729+
}
730+
731+
txResponse, err := proto.Marshal(&sdk.TxMsgData{
732+
Data: []*sdk.MsgData{msgData},
733+
})
734+
suite.Require().NoError(err)
735+
736+
deliverTx := abcitypes.ResponseDeliverTx{
737+
Data: txResponse,
738+
}
739+
responses := tmprotostate.ABCIResponses{
740+
DeliverTxs: []*abcitypes.ResponseDeliverTx{
741+
&deliverTx,
742+
},
743+
}
744+
745+
differentMsgResponseBz, err := proto.Marshal(&channeltypes.MsgRecvPacketResponse{})
746+
suite.Require().NoError(err)
747+
748+
differentMsgData := &sdk.MsgData{
749+
MsgType: sdk.MsgTypeURL(&channeltypes.MsgRecvPacket{}),
750+
Data: differentMsgResponseBz,
751+
}
752+
753+
differentTxResponse, err := proto.Marshal(&sdk.TxMsgData{
754+
Data: []*sdk.MsgData{differentMsgData},
755+
})
756+
suite.Require().NoError(err)
757+
758+
differentDeliverTx := abcitypes.ResponseDeliverTx{
759+
Data: differentTxResponse,
760+
}
761+
762+
differentResponses := tmprotostate.ABCIResponses{
763+
DeliverTxs: []*abcitypes.ResponseDeliverTx{
764+
&differentDeliverTx,
765+
},
766+
}
767+
768+
hash := tmstate.ABCIResponsesResultsHash(&responses)
769+
differentHash := tmstate.ABCIResponsesResultsHash(&differentResponses)
770+
771+
suite.Require().NotEqual(hash, differentHash)
772+
}

modules/apps/27-interchain-accounts/host/keeper/relay.go

+45-21
Original file line numberDiff line numberDiff line change
@@ -3,66 +3,89 @@ package keeper
33
import (
44
sdk "github.com/cosmos/cosmos-sdk/types"
55
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
6+
"github.com/gogo/protobuf/proto"
67

78
"github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/host/types"
89
icatypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/types"
910
channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types"
1011
)
1112

12-
// OnRecvPacket handles a given interchain accounts packet on a destination host chain
13-
func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet) error {
13+
// OnRecvPacket handles a given interchain accounts packet on a destination host chain.
14+
// If the transaction is successfully executed, the transaction response bytes will be returned.
15+
func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet) ([]byte, error) {
1416
var data icatypes.InterchainAccountPacketData
1517

1618
if err := icatypes.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil {
1719
// UnmarshalJSON errors are indeterminate and therefore are not wrapped and included in failed acks
18-
return sdkerrors.Wrapf(icatypes.ErrUnknownDataType, "cannot unmarshal ICS-27 interchain account packet data")
20+
return nil, sdkerrors.Wrapf(icatypes.ErrUnknownDataType, "cannot unmarshal ICS-27 interchain account packet data")
1921
}
2022

2123
switch data.Type {
2224
case icatypes.EXECUTE_TX:
2325
msgs, err := icatypes.DeserializeCosmosTx(k.cdc, data.Data)
2426
if err != nil {
25-
return err
27+
return nil, err
2628
}
2729

28-
if err = k.executeTx(ctx, packet.SourcePort, packet.DestinationPort, packet.DestinationChannel, msgs); err != nil {
29-
return err
30+
txResponse, err := k.executeTx(ctx, packet.SourcePort, packet.DestinationPort, packet.DestinationChannel, msgs)
31+
if err != nil {
32+
return nil, err
3033
}
3134

32-
return nil
35+
return txResponse, nil
3336
default:
34-
return icatypes.ErrUnknownDataType
37+
return nil, icatypes.ErrUnknownDataType
3538
}
3639
}
3740

38-
func (k Keeper) executeTx(ctx sdk.Context, sourcePort, destPort, destChannel string, msgs []sdk.Msg) error {
41+
// executeTx attempts to execute the provided transaction. It begins by authenticating the transaction signer.
42+
// If authentication succeeds, it does basic validation of the messages before attempting to deliver each message
43+
// into state. The state changes will only be committed if all messages in the transaction succeed. Thus the
44+
// execution of the transaction is atomic, all state changes are reverted if a single message fails.
45+
func (k Keeper) executeTx(ctx sdk.Context, sourcePort, destPort, destChannel string, msgs []sdk.Msg) ([]byte, error) {
3946
channel, found := k.channelKeeper.GetChannel(ctx, destPort, destChannel)
4047
if !found {
41-
return channeltypes.ErrChannelNotFound
48+
return nil, channeltypes.ErrChannelNotFound
4249
}
4350

4451
if err := k.authenticateTx(ctx, msgs, channel.ConnectionHops[0], sourcePort); err != nil {
45-
return err
52+
return nil, err
53+
}
54+
55+
txMsgData := &sdk.TxMsgData{
56+
Data: make([]*sdk.MsgData, len(msgs)),
4657
}
4758

4859
// CacheContext returns a new context with the multi-store branched into a cached storage object
4960
// writeCache is called only if all msgs succeed, performing state transitions atomically
5061
cacheCtx, writeCache := ctx.CacheContext()
51-
for _, msg := range msgs {
62+
for i, msg := range msgs {
5263
if err := msg.ValidateBasic(); err != nil {
53-
return err
64+
return nil, err
65+
}
66+
67+
msgResponse, err := k.executeMsg(cacheCtx, msg)
68+
if err != nil {
69+
return nil, err
5470
}
5571

56-
if err := k.executeMsg(cacheCtx, msg); err != nil {
57-
return err
72+
txMsgData.Data[i] = &sdk.MsgData{
73+
MsgType: sdk.MsgTypeURL(msg),
74+
Data: msgResponse,
5875
}
76+
5977
}
6078

6179
// NOTE: The context returned by CacheContext() creates a new EventManager, so events must be correctly propagated back to the current context
6280
ctx.EventManager().EmitEvents(cacheCtx.EventManager().Events())
6381
writeCache()
6482

65-
return nil
83+
txResponse, err := proto.Marshal(txMsgData)
84+
if err != nil {
85+
return nil, sdkerrors.Wrap(err, "failed to marshal tx data")
86+
}
87+
88+
return txResponse, nil
6689
}
6790

6891
// authenticateTx ensures the provided msgs contain the correct interchain account signer address retrieved
@@ -89,20 +112,21 @@ func (k Keeper) authenticateTx(ctx sdk.Context, msgs []sdk.Msg, connectionID, po
89112
return nil
90113
}
91114

92-
// Attempts to get the message handler from the router and if found will then execute the message
93-
func (k Keeper) executeMsg(ctx sdk.Context, msg sdk.Msg) error {
115+
// Attempts to get the message handler from the router and if found will then execute the message.
116+
// If the message execution is successful, the proto marshaled message response will be returned.
117+
func (k Keeper) executeMsg(ctx sdk.Context, msg sdk.Msg) ([]byte, error) {
94118
handler := k.msgRouter.Handler(msg)
95119
if handler == nil {
96-
return icatypes.ErrInvalidRoute
120+
return nil, icatypes.ErrInvalidRoute
97121
}
98122

99123
res, err := handler(ctx, msg)
100124
if err != nil {
101-
return err
125+
return nil, err
102126
}
103127

104128
// NOTE: The sdk msg handler creates a new EventManager, so events must be correctly propagated back to the current context
105129
ctx.EventManager().EmitEvents(res.GetEvents())
106130

107-
return nil
131+
return res.Data, nil
108132
}

0 commit comments

Comments
 (0)