Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add standard acknowledgement to channels and apply usage in ibc transfer #7263

Merged
merged 19 commits into from
Sep 9, 2020
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/ibc/custom.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,9 @@ receive acknowledegments with the IBC modules as byte strings.

Thus, modules must agree on how to encode/decode acknowledgements. The process of creating an
acknowledgement struct along with encoding and decoding it, is very similar to the packet data
example above.
example above. [ICS 04](https://github.com/cosmos/ics/tree/master/spec/ics-004-channel-and-packet-semantics#acknowledgement-envelope)
specifies a recommended format for acknowledgements. This acknowledgement type can be imported from
[channel types](https://github.com/cosmos/cosmos-sdk/tree/master/x/ibc/04-channel/types).

#### Acknowledging Packets

Expand Down
79 changes: 47 additions & 32 deletions proto/ibc/channel/channel.proto
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ message MsgChannelOpenTry {
Channel channel = 3 [(gogoproto.nullable) = false];
string counterparty_version = 4
[(gogoproto.moretags) = "yaml:\"counterparty_version\""];
bytes proof_init = 5 [(gogoproto.moretags) = "yaml:\"proof_init\""];
bytes proof_init = 5 [(gogoproto.moretags) = "yaml:\"proof_init\""];
ibc.client.Height proof_height = 6 [
(gogoproto.moretags) = "yaml:\"proof_height\"",
(gogoproto.nullable) = false
];
bytes signer = 7
bytes signer = 7
[(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"];
}

Expand All @@ -40,30 +40,30 @@ message MsgChannelOpenAck {
string channel_id = 2 [(gogoproto.moretags) = "yaml:\"channel_id\""];
string counterparty_version = 3
[(gogoproto.moretags) = "yaml:\"counterparty_version\""];
bytes proof_try = 4 [(gogoproto.moretags) = "yaml:\"proof_try\""];
bytes proof_try = 4 [(gogoproto.moretags) = "yaml:\"proof_try\""];
ibc.client.Height proof_height = 5 [
(gogoproto.moretags) = "yaml:\"proof_height\"",
(gogoproto.nullable) = false
];
bytes signer = 6
bytes signer = 6
[(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"];
}

// MsgChannelOpenConfirm defines a msg sent by a Relayer to Chain B to
// acknowledge the change of channel state to OPEN on Chain A.
message MsgChannelOpenConfirm {
string port_id = 1 [(gogoproto.moretags) = "yaml:\"port_id\""];
string channel_id = 2 [(gogoproto.moretags) = "yaml:\"channel_id\""];
bytes proof_ack = 3 [(gogoproto.moretags) = "yaml:\"proof_ack\""];
string port_id = 1 [(gogoproto.moretags) = "yaml:\"port_id\""];
string channel_id = 2 [(gogoproto.moretags) = "yaml:\"channel_id\""];
bytes proof_ack = 3 [(gogoproto.moretags) = "yaml:\"proof_ack\""];
ibc.client.Height proof_height = 4 [
(gogoproto.moretags) = "yaml:\"proof_height\"",
(gogoproto.nullable) = false
];
bytes signer = 5
bytes signer = 5
[(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"];
}

// MsgChannelOpenConfirm defines a msg sent by a Relayer to Chain A
// MsgChannelCloseInit defines a msg sent by a Relayer to Chain A
// to close a channel with Chain B.
message MsgChannelCloseInit {
string port_id = 1 [(gogoproto.moretags) = "yaml:\"port_id\""];
Expand All @@ -75,33 +75,33 @@ message MsgChannelCloseInit {
// MsgChannelCloseConfirm defines a msg sent by a Relayer to Chain B
// to acknowledge the change of channel state to CLOSED on Chain A.
message MsgChannelCloseConfirm {
string port_id = 1 [(gogoproto.moretags) = "yaml:\"port_id\""];
string channel_id = 2 [(gogoproto.moretags) = "yaml:\"channel_id\""];
bytes proof_init = 3 [(gogoproto.moretags) = "yaml:\"proof_init\""];
string port_id = 1 [(gogoproto.moretags) = "yaml:\"port_id\""];
string channel_id = 2 [(gogoproto.moretags) = "yaml:\"channel_id\""];
bytes proof_init = 3 [(gogoproto.moretags) = "yaml:\"proof_init\""];
ibc.client.Height proof_height = 4 [
(gogoproto.moretags) = "yaml:\"proof_height\"",
(gogoproto.nullable) = false
];
bytes signer = 5
bytes signer = 5
[(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"];
}

// MsgRecvPacket receives incoming IBC packet
message MsgRecvPacket {
Packet packet = 1 [(gogoproto.nullable) = false];
bytes proof = 2;
Packet packet = 1 [(gogoproto.nullable) = false];
bytes proof = 2;
ibc.client.Height proof_height = 3 [
(gogoproto.moretags) = "yaml:\"proof_height\"",
(gogoproto.nullable) = false
];
bytes signer = 4
bytes signer = 4
[(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"];
}

// MsgTimeout receives timed-out packet
message MsgTimeout {
Packet packet = 1 [(gogoproto.nullable) = false];
bytes proof = 2;
Packet packet = 1 [(gogoproto.nullable) = false];
bytes proof = 2;
ibc.client.Height proof_height = 3 [
(gogoproto.moretags) = "yaml:\"proof_height\"",
(gogoproto.nullable) = false
Expand All @@ -114,9 +114,9 @@ message MsgTimeout {

// MsgTimeoutOnClose timed-out packet upon counterparty channel closure.
message MsgTimeoutOnClose {
Packet packet = 1 [(gogoproto.nullable) = false];
bytes proof = 2;
bytes proof_close = 3 [(gogoproto.moretags) = "yaml:\"proof_close\""];
Packet packet = 1 [(gogoproto.nullable) = false];
bytes proof = 2;
bytes proof_close = 3 [(gogoproto.moretags) = "yaml:\"proof_close\""];
ibc.client.Height proof_height = 4 [
(gogoproto.moretags) = "yaml:\"proof_height\"",
(gogoproto.nullable) = false
Expand All @@ -129,14 +129,14 @@ message MsgTimeoutOnClose {

// MsgAcknowledgement receives incoming IBC acknowledgement
message MsgAcknowledgement {
Packet packet = 1 [(gogoproto.nullable) = false];
bytes acknowledgement = 2;
bytes proof = 3;
Packet packet = 1 [(gogoproto.nullable) = false];
bytes acknowledgement = 2;
bytes proof = 3;
ibc.client.Height proof_height = 4 [
(gogoproto.moretags) = "yaml:\"proof_height\"",
(gogoproto.nullable) = false
];
bytes signer = 5
bytes signer = 5
[(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"];
}

Expand All @@ -152,8 +152,8 @@ message Channel {
Order ordering = 2;
// counterparty channel end
Counterparty counterparty = 3 [(gogoproto.nullable) = false];
// list of connection identifiers, in order, along which packets sent on this
// channel will travel
// list of connection identifiers, in order, along which packets sent on
// this channel will travel
repeated string connection_hops = 4
[(gogoproto.moretags) = "yaml:\"connection_hops\""];
// opaque channel version, which is agreed upon during the handshake
Expand All @@ -171,8 +171,8 @@ message IdentifiedChannel {
Order ordering = 2;
// counterparty channel end
Counterparty counterparty = 3 [(gogoproto.nullable) = false];
// list of connection identifiers, in order, along which packets sent on this
// channel will travel
// list of connection identifiers, in order, along which packets sent on
// this channel will travel
repeated string connection_hops = 4
[(gogoproto.moretags) = "yaml:\"connection_hops\""];
// opaque channel version, which is agreed upon during the handshake
Expand Down Expand Up @@ -230,9 +230,9 @@ message Counterparty {
message Packet {
option (gogoproto.goproto_getters) = false;

// number corresponds to the order of sends and receives, where a Packet with
// an earlier sequence number must be sent and received before a Packet with a
// later sequence number.
// number corresponds to the order of sends and receives, where a Packet
// with an earlier sequence number must be sent and received before a Packet
// with a later sequence number.
uint64 sequence = 1;
// identifies the port on the sending chain.
string source_port = 2 [(gogoproto.moretags) = "yaml:\"source_port\""];
Expand Down Expand Up @@ -270,3 +270,18 @@ message PacketAckCommitment {
// packet commitment hash.
bytes hash = 4;
}

// Acknowledgement is the recommended acknowledgement format to be used by
// app-specifc protocols.
cwgoes marked this conversation as resolved.
Show resolved Hide resolved
// NOTE: The field numbers 21 and 22 were explicitly chosen to avoid accidental
// conflicts with other protobuf message formats used for acknowledgements.
// The first byte of any message with this format will be the non-ASCII values
// `0xaa` (result) or `0xb2` (error). Implemented as defined by ICS:
// https://github.com/cosmos/ics/tree/master/spec/ics-004-channel-and-packet-semantics#acknowledgement-envelope
message Acknowledgement {
// response contains either a result or an error and must be non-empty
oneof response {
bytes result = 21;
string error = 22;
}
}
42 changes: 17 additions & 25 deletions proto/ibc/transfer/transfer.proto
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ message MsgTransfer {
// the tokens to be transferred
cosmos.base.v1beta1.Coin token = 3 [(gogoproto.nullable) = false];
// the sender address
bytes sender = 4 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"];
bytes sender = 4
[(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"];
// the recipient address on the destination chain
string receiver = 5;
// Timeout height relative to the current block height.
Expand All @@ -29,7 +30,8 @@ message MsgTransfer {
];
// Timeout timestamp (in nanoseconds) relative to the current block timestamp.
// The timeout is disabled when set to 0.
uint64 timeout_timestamp = 7 [(gogoproto.moretags) = "yaml:\"timeout_timestamp\""];
uint64 timeout_timestamp = 7
[(gogoproto.moretags) = "yaml:\"timeout_timestamp\""];
}

// FungibleTokenPacketData defines a struct for the packet payload
Expand All @@ -46,35 +48,25 @@ message FungibleTokenPacketData {
string receiver = 4;
}

// FungibleTokenPacketAcknowledgement contains a boolean success flag and an
// optional error msg error msg is empty string on success See spec for
// onAcknowledgePacket:
// https://github.com/cosmos/ics/tree/master/spec/ics-020-fungible-token-transfer#packet-relay
message FungibleTokenPacketAcknowledgement {
bool success = 1;
string error = 2;
}

// DenomTrace contains the base denomination for ICS20 fungible tokens and the source tracing
// information path.
// DenomTrace contains the base denomination for ICS20 fungible tokens and the
// source tracing information path.
message DenomTrace {
// path defines the chain of port/channel identifiers used for tracing the source of the fungible
// token.
// path defines the chain of port/channel identifiers used for tracing the
// source of the fungible token.
string path = 1;
// base denomination of the relayed fungible token.
string base_denom = 2;
}

// Params defines the set of IBC transfer parameters.
// NOTE: To prevent a single token from being transferred, set the TransfersEnabled parameter to
// true and then set the bank module's SendEnabled parameter for the denomination to false.
// NOTE: To prevent a single token from being transferred, set the
// TransfersEnabled parameter to true and then set the bank module's SendEnabled
// parameter for the denomination to false.
message Params {
// send_enabled enables or disables all cross-chain token transfers from this chain.
bool send_enabled = 1 [
(gogoproto.moretags) = "yaml:\"send_enabled\""
];
// receive_enabled enables or disables all cross-chain token transfers to this chain.
bool receive_enabled = 2 [
(gogoproto.moretags) = "yaml:\"receive_enabled\""
];
// send_enabled enables or disables all cross-chain token transfers from this
// chain.
bool send_enabled = 1 [(gogoproto.moretags) = "yaml:\"send_enabled\""];
// receive_enabled enables or disables all cross-chain token transfers to this
// chain.
bool receive_enabled = 2 [(gogoproto.moretags) = "yaml:\"receive_enabled\""];
}
2 changes: 1 addition & 1 deletion x/ibc-transfer/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func (suite *TransferTestSuite) TestHandleMsgTransfer() {
// relay send
fungibleTokenPacket := types.NewFungibleTokenPacketData(coinToSendToB.Denom, coinToSendToB.Amount.Uint64(), suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String())
packet := channeltypes.NewPacket(fungibleTokenPacket.GetBytes(), 1, channelA.PortID, channelA.ID, channelB.PortID, channelB.ID, timeoutHeight, 0)
ack := types.FungibleTokenPacketAcknowledgement{Success: true}
ack := channeltypes.NewResultAcknowledgement([]byte{byte(1)})
err = suite.coordinator.RelayPacket(suite.chainA, suite.chainB, clientA, clientB, packet, ack.GetBytes())
suite.Require().NoError(err) // relay committed

Expand Down
8 changes: 5 additions & 3 deletions x/ibc-transfer/keeper/relay.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,11 +232,13 @@ func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, data t
// acknowledgement written on the receiving chain. If the acknowledgement
// was a success then nothing occurs. If the acknowledgement failed, then
// the sender is refunded their tokens using the refundPacketToken function.
func (k Keeper) OnAcknowledgementPacket(ctx sdk.Context, packet channeltypes.Packet, data types.FungibleTokenPacketData, ack types.FungibleTokenPacketAcknowledgement) error {
if !ack.Success {
func (k Keeper) OnAcknowledgementPacket(ctx sdk.Context, packet channeltypes.Packet, data types.FungibleTokenPacketData, ack channeltypes.Acknowledgement) error {
switch ack.Response.(type) {
case *channeltypes.Acknowledgement_Error:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we test this somehow?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes I think it should be tested by the commented tests. I wanted to uncomment the tests in a followup pr to keep this pr lightweight. There is an issue for this #7261

return k.refundPacketToken(ctx, packet, data)
default:
return nil
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, can you add a note on why we return nil instead of an error?

}
return nil
}

// OnTimeoutPacket refunds the sender since the original packet sent was
Expand Down
13 changes: 4 additions & 9 deletions x/ibc-transfer/keeper/relay_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() {
// relay send packet
fungibleTokenPacket := types.NewFungibleTokenPacketData(coinFromBToA, suite.chainB.SenderAccount.GetAddress().String(), suite.chainA.SenderAccount.GetAddress().String())
packet := channeltypes.NewPacket(fungibleTokenPacket.GetBytes(), 1, channelB.PortID, channelB.ID, channelA.PortID, channelA.ID, 110, 0)
ack := types.FungibleTokenPacketAcknowledgement{Success: true}
ack := channeltypes.NewResultAcknowledgement([]byte{byte(1)})
err = suite.coordinator.RelayPacket(suite.chainB, suite.chainA, clientB, clientA, packet, ack.GetBytes())
suite.Require().NoError(err) // relay committed

Expand Down Expand Up @@ -229,21 +229,16 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() {
// to chainB.
func (suite *KeeperTestSuite) TestOnAcknowledgementPacket() {
var (
successAck = types.FungibleTokenPacketAcknowledgement{
Success: true,
}
failedAck = types.FungibleTokenPacketAcknowledgement{
Success: false,
Error: "failed packet transfer",
}
successAck = channeltypes.NewResultAcknowledgement([]byte{byte(1)})
failedAck = channeltypes.NewErrorAcknowledgement("failed packet transfer")

channelA, channelB ibctesting.TestChannel
coins sdk.Coins
)

testCases := []struct {
msg string
ack types.FungibleTokenPacketAcknowledgement
ack channeltypes.Acknowledgement
malleate func()
source bool
success bool // success of ack
Expand Down
30 changes: 17 additions & 13 deletions x/ibc-transfer/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,16 +305,11 @@ func (am AppModule) OnRecvPacket(
return nil, nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet data: %s", err.Error())
}

acknowledgement := types.FungibleTokenPacketAcknowledgement{
Success: true,
Error: "",
}
acknowledgement := channeltypes.NewResultAcknowledgement([]byte{byte(1)})
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure what to put here since it is not specified in ICS20 at the moment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know either, but this seems like a fair non-empty success flag.
(Also 1 is a nice replacement for true and auto-convert in many languages)


if err := am.keeper.OnRecvPacket(ctx, packet, data); err != nil {
acknowledgement = types.FungibleTokenPacketAcknowledgement{
Success: false,
Error: err.Error(),
}
err := am.keeper.OnRecvPacket(ctx, packet, data)
if err != nil {
acknowledgement = channeltypes.NewErrorAcknowledgement(err.Error())
}

ctx.EventManager().EmitEvent(
Expand All @@ -324,6 +319,7 @@ func (am AppModule) OnRecvPacket(
sdk.NewAttribute(types.AttributeKeyReceiver, data.Receiver),
sdk.NewAttribute(types.AttributeKeyDenom, data.Denom),
sdk.NewAttribute(types.AttributeKeyAmount, fmt.Sprintf("%d", data.Amount)),
sdk.NewAttribute(types.AttributeKeyAckSuccess, fmt.Sprintf("%t", err != nil)),
),
)

Expand All @@ -338,7 +334,7 @@ func (am AppModule) OnAcknowledgementPacket(
packet channeltypes.Packet,
acknowledgement []byte,
) (*sdk.Result, error) {
var ack types.FungibleTokenPacketAcknowledgement
var ack channeltypes.Acknowledgement
if err := types.ModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we use the cdc in the keeper?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the keeper codec is a BinaryMarshaler, thus it doesn't support JSON unmarshal

return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet acknowledgement: %v", err)
}
Expand All @@ -358,15 +354,23 @@ func (am AppModule) OnAcknowledgementPacket(
sdk.NewAttribute(types.AttributeKeyReceiver, data.Receiver),
sdk.NewAttribute(types.AttributeKeyDenom, data.Denom),
sdk.NewAttribute(types.AttributeKeyAmount, fmt.Sprintf("%d", data.Amount)),
sdk.NewAttribute(types.AttributeKeyAckSuccess, fmt.Sprintf("%t", ack.Success)),
sdk.NewAttribute(types.AttributeKeyAck, fmt.Sprintf("%v", ack)),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thoughts? or should I add a switch to check the type and emit a bool?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah we should

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will have it emit the result or error message as well

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of the bool

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated, let me know if you agree, with specs as well

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the usage above:

sdk.NewAttribute(types.AttributeKeyAckSuccess, fmt.Sprintf("%t", err != nil))

We could use %v instead (or as well), but it would be nice to have them both the same (the attributes in the two different functions)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah yea, I agree. Will update in a followup. We discussed standardizing IBC events on the IBC call which I think would be very useful in cases like this where there are multiple way to represent the same information

),
)

if !ack.Success {
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, ack.Error),
sdk.NewAttribute(types.AttributeKeyAckError, resp.Error),
),
)
}
Expand Down
Loading