Skip to content

Commit

Permalink
chore: replace error string in transfer acks with const (#818)
Browse files Browse the repository at this point in the history
* fix: adding ack error string const for transfer

* updating godoc

* adding warning note to godoc in 04-channel

* updating to include abci error code, and copy tests from ica

* adding changelog entry

(cherry picked from commit ac46ac0)

# Conflicts:
#	modules/apps/27-interchain-accounts/host/types/ack.go
#	modules/apps/transfer/ibc_module.go
  • Loading branch information
damiannolan authored and mergify-bot committed Feb 23, 2022
1 parent 446ff37 commit 2e51bab
Show file tree
Hide file tree
Showing 6 changed files with 439 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ Ref: https://keepachangelog.com/en/1.0.0/

## [v2.0.3](https://github.com/cosmos/ibc-go/releases/tag/v2.0.2) - 2022-02-03

* (transfer) [\#818](https://github.com/cosmos/ibc-go/pull/818) Error acknowledgements returned from Transfer `OnRecvPacket` now include a deterministic ABCI code and error message.

### Improvements

* (channel) [\#692](https://github.com/cosmos/ibc-go/pull/692) Minimize channel logging by only emitting the packet sequence, source port/channel, destination port/channel upon packet receives, acknowledgements and timeouts.
Expand Down
27 changes: 27 additions & 0 deletions modules/apps/27-interchain-accounts/host/types/ack.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package types

import (
"fmt"

sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"

channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types"
)

const (
// ackErrorString defines a string constant included in error acknowledgements
// NOTE: Changing this const is state machine breaking as acknowledgements are written into state
ackErrorString = "error handling packet on host chain: see events for details"
)

// NewErrorAcknowledgement returns a deterministic error string which may be used in
// the packet acknowledgement.
func NewErrorAcknowledgement(err error) channeltypes.Acknowledgement {
// the ABCI code is included in the abcitypes.ResponseDeliverTx hash
// constructed in Tendermint and is therefore determinstic
_, code, _ := sdkerrors.ABCIInfo(err, false) // discard non-deterministic codespace and log values

errorString := fmt.Sprintf("ABCI code: %d: %s", code, ackErrorString)

return channeltypes.NewErrorAcknowledgement(errorString)
}
280 changes: 280 additions & 0 deletions modules/apps/transfer/ibc_module.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
package transfer

import (
"fmt"
"math"

sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types"

"github.com/cosmos/ibc-go/v3/modules/apps/transfer/keeper"
"github.com/cosmos/ibc-go/v3/modules/apps/transfer/types"
channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types"
porttypes "github.com/cosmos/ibc-go/v3/modules/core/05-port/types"
host "github.com/cosmos/ibc-go/v3/modules/core/24-host"
ibcexported "github.com/cosmos/ibc-go/v3/modules/core/exported"
)

// IBCModule implements the ICS26 interface for transfer given the transfer keeper.
type IBCModule struct {
keeper keeper.Keeper
}

// NewIBCModule creates a new IBCModule given the keeper
func NewIBCModule(k keeper.Keeper) IBCModule {
return IBCModule{
keeper: k,
}
}

// ValidateTransferChannelParams does validation of a newly created transfer channel. A transfer
// channel must be UNORDERED, use the correct port (by default 'transfer'), and use the current
// supported version. Only 2^32 channels are allowed to be created.
func ValidateTransferChannelParams(
ctx sdk.Context,
keeper keeper.Keeper,
order channeltypes.Order,
portID string,
channelID string,
) error {
// NOTE: for escrow address security only 2^32 channels are allowed to be created
// Issue: https://github.com/cosmos/cosmos-sdk/issues/7737
channelSequence, err := channeltypes.ParseChannelSequence(channelID)
if err != nil {
return err
}
if channelSequence > uint64(math.MaxUint32) {
return sdkerrors.Wrapf(types.ErrMaxTransferChannels, "channel sequence %d is greater than max allowed transfer channels %d", channelSequence, uint64(math.MaxUint32))
}
if order != channeltypes.UNORDERED {
return sdkerrors.Wrapf(channeltypes.ErrInvalidChannelOrdering, "expected %s channel, got %s ", channeltypes.UNORDERED, order)
}

// Require portID is the portID transfer module is bound to
boundPort := keeper.GetPort(ctx)
if boundPort != portID {
return sdkerrors.Wrapf(porttypes.ErrInvalidPort, "invalid port: %s, expected %s", portID, boundPort)
}

return nil
}

// OnChanOpenInit implements the IBCModule interface
func (im IBCModule) OnChanOpenInit(
ctx sdk.Context,
order channeltypes.Order,
connectionHops []string,
portID string,
channelID string,
chanCap *capabilitytypes.Capability,
counterparty channeltypes.Counterparty,
version string,
) error {
if err := ValidateTransferChannelParams(ctx, im.keeper, order, portID, channelID); err != nil {
return err
}

if version != types.Version {
return sdkerrors.Wrapf(types.ErrInvalidVersion, "got %s, expected %s", version, types.Version)
}

// Claim channel capability passed back by IBC module
if err := im.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil {
return err
}

return nil
}

// OnChanOpenTry implements the IBCModule interface.
func (im IBCModule) OnChanOpenTry(
ctx sdk.Context,
order channeltypes.Order,
connectionHops []string,
portID,
channelID string,
chanCap *capabilitytypes.Capability,
counterparty channeltypes.Counterparty,
counterpartyVersion string,
) (string, error) {
if err := ValidateTransferChannelParams(ctx, im.keeper, order, portID, channelID); err != nil {
return "", err
}

if counterpartyVersion != types.Version {
return "", sdkerrors.Wrapf(types.ErrInvalidVersion, "invalid counterparty version: got: %s, expected %s", counterpartyVersion, types.Version)
}

// Module may have already claimed capability in OnChanOpenInit in the case of crossing hellos
// (ie chainA and chainB both call ChanOpenInit before one of them calls ChanOpenTry)
// If module can already authenticate the capability then module already owns it so we don't need to claim
// Otherwise, module does not have channel capability and we must claim it from IBC
if !im.keeper.AuthenticateCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)) {
// Only claim channel capability passed back by IBC module if we do not already own it
if err := im.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil {
return "", err
}
}

return types.Version, nil
}

// OnChanOpenAck implements the IBCModule interface
func (im IBCModule) OnChanOpenAck(
ctx sdk.Context,
portID,
channelID string,
counterpartyVersion string,
) error {
if counterpartyVersion != types.Version {
return sdkerrors.Wrapf(types.ErrInvalidVersion, "invalid counterparty version: %s, expected %s", counterpartyVersion, types.Version)
}
return nil
}

// OnChanOpenConfirm implements the IBCModule interface
func (im IBCModule) OnChanOpenConfirm(
ctx sdk.Context,
portID,
channelID string,
) error {
return nil
}

// OnChanCloseInit implements the IBCModule interface
func (im IBCModule) OnChanCloseInit(
ctx sdk.Context,
portID,
channelID string,
) error {
// Disallow user-initiated channel closing for transfer channels
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "user cannot close channel")
}

// OnChanCloseConfirm implements the IBCModule interface
func (im IBCModule) OnChanCloseConfirm(
ctx sdk.Context,
portID,
channelID string,
) error {
return nil
}

// OnRecvPacket implements the IBCModule interface. A successful acknowledgement
// is returned if the packet data is succesfully decoded and the receive application
// logic returns without error.
func (im IBCModule) OnRecvPacket(
ctx sdk.Context,
packet channeltypes.Packet,
relayer sdk.AccAddress,
) ibcexported.Acknowledgement {
ack := channeltypes.NewResultAcknowledgement([]byte{byte(1)})

var data types.FungibleTokenPacketData
if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil {
ack = channeltypes.NewErrorAcknowledgement("cannot unmarshal ICS-20 transfer packet data")
}

// only attempt the application logic if the packet data
// was successfully decoded
if ack.Success() {
err := im.keeper.OnRecvPacket(ctx, packet, data)
if err != nil {
ack = types.NewErrorAcknowledgement(err)
}
}

ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypePacket,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute(types.AttributeKeyReceiver, data.Receiver),
sdk.NewAttribute(types.AttributeKeyDenom, data.Denom),
sdk.NewAttribute(types.AttributeKeyAmount, data.Amount),
sdk.NewAttribute(types.AttributeKeyAckSuccess, fmt.Sprintf("%t", ack.Success())),
),
)

// NOTE: acknowledgement will be written synchronously during IBC handler execution.
return ack
}

// OnAcknowledgementPacket implements the IBCModule interface
func (im IBCModule) OnAcknowledgementPacket(
ctx sdk.Context,
packet channeltypes.Packet,
acknowledgement []byte,
relayer sdk.AccAddress,
) error {
var ack channeltypes.Acknowledgement
if err := types.ModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet acknowledgement: %v", err)
}
var data types.FungibleTokenPacketData
if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet data: %s", err.Error())
}

if err := im.keeper.OnAcknowledgementPacket(ctx, packet, data, ack); err != nil {
return err
}

ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypePacket,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute(types.AttributeKeyReceiver, data.Receiver),
sdk.NewAttribute(types.AttributeKeyDenom, data.Denom),
sdk.NewAttribute(types.AttributeKeyAmount, data.Amount),
sdk.NewAttribute(types.AttributeKeyAck, ack.String()),
),
)

switch resp := ack.Response.(type) {
case *channeltypes.Acknowledgement_Result:
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypePacket,
sdk.NewAttribute(types.AttributeKeyAckSuccess, string(resp.Result)),
),
)
case *channeltypes.Acknowledgement_Error:
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypePacket,
sdk.NewAttribute(types.AttributeKeyAckError, resp.Error),
),
)
}

return nil
}

// OnTimeoutPacket implements the IBCModule interface
func (im IBCModule) OnTimeoutPacket(
ctx sdk.Context,
packet channeltypes.Packet,
relayer sdk.AccAddress,
) error {
var data types.FungibleTokenPacketData
if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet data: %s", err.Error())
}
// refund tokens
if err := im.keeper.OnTimeoutPacket(ctx, packet, data); err != nil {
return err
}

ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeTimeout,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute(types.AttributeKeyRefundReceiver, data.Sender),
sdk.NewAttribute(types.AttributeKeyRefundDenom, data.Denom),
sdk.NewAttribute(types.AttributeKeyRefundAmount, data.Amount),
),
)

return nil
}
27 changes: 27 additions & 0 deletions modules/apps/transfer/types/ack.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package types

import (
"fmt"

sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"

channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types"
)

const (
// ackErrorString defines a string constant included in error acknowledgements
// NOTE: Changing this const is state machine breaking as acknowledgements are written into state
ackErrorString = "error handling packet on destination chain: see events for details"
)

// NewErrorAcknowledgement returns a deterministic error string which may be used in
// the packet acknowledgement.
func NewErrorAcknowledgement(err error) channeltypes.Acknowledgement {
// the ABCI code is included in the abcitypes.ResponseDeliverTx hash
// constructed in Tendermint and is therefore deterministic
_, code, _ := sdkerrors.ABCIInfo(err, false) // discard non-determinstic codespace and log values

errorString := fmt.Sprintf("ABCI code: %d: %s", code, ackErrorString)

return channeltypes.NewErrorAcknowledgement(errorString)
}
Loading

0 comments on commit 2e51bab

Please sign in to comment.